diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 31f6a4a1bfc..476271a15a5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,18 +1,23 @@ -# Code owners file. -# This file controls who is tagged for review for any given pull request. +# Code owners file + +# This file controls who is tagged for review for any given pull request # The java-samples-reviewers team is the default owner for anything not -# explicitly taken by someone else. -* @GoogleCloudPlatform/java-samples-reviewers @yoshi-approver +# explicitly taken by someone else + +* @GoogleCloudPlatform/java-samples-reviewers @yoshi-approver /bigtable/**/*.java @GoogleCloudPlatform/cloud-native-db-dpes @GoogleCloudPlatform/java-samples-reviewers @yoshi-approver /cloud-sql/**/*.java @GoogleCloudPlatform/infra-db-dpes @GoogleCloudPlatform/java-samples-reviewers @yoshi-approver /datastore/**/*.java @GoogleCloudPlatform/cloud-native-db-dpes @GoogleCloudPlatform/java-samples-reviewers @yoshi-approver /firestore/**/*.java @GoogleCloudPlatform/cloud-native-db-dpes @GoogleCloudPlatform/java-samples-reviewers @yoshi-approver /iot/ @gcseh @GoogleCloudPlatform/api-iot @GoogleCloudPlatform/java-samples-reviewers @yoshi-approver -/logging/ @GoogleCloudPlatform/observability-devx @GoogleCloudPlatform/java-samples-reviewers @yoshi-approver +/logging/ @GoogleCloudPlatform/dee-platform-ops @GoogleCloudPlatform/java-samples-reviewers @yoshi-approver /pubsub/ @GoogleCloudPlatform/api-pubsub-and-pubsublite @GoogleCloudPlatform/java-samples-reviewers @yoshi-approver /pubsublite/ @GoogleCloudPlatform/api-pubsub-and-pubsublite @GoogleCloudPlatform/java-samples-reviewers @yoshi-approver /storage/**/*.java @GoogleCloudPlatform/cloud-storage-dpes @GoogleCloudPlatform/java-samples-reviewers @yoshi-approver .github/auto-approve.yml @googleapis/github-automation/ @sofisl @GoogleCloudPlatform/java-samples-reviewers +/asset/ @GoogleCloudPlatform/dee-infra @GoogleCloudPlatform/java-samples-reviewers @yoshi-approver +/errorreporting/ @GoogleCloudPlatform/dee-platform-ops @GoogleCloudPlatform/java-samples-reviewers @yoshi-approver +/monitoring/ @GoogleCloudPlatform/dee-platform-ops @GoogleCloudPlatform/java-samples-reviewers @yoshi-approver diff --git a/.kokoro/tests/run_tests.sh b/.kokoro/tests/run_tests.sh index a7d7071d0d3..9c19a857ff7 100755 --- a/.kokoro/tests/run_tests.sh +++ b/.kokoro/tests/run_tests.sh @@ -74,7 +74,8 @@ if [[ "$SCRIPT_DEBUG" != "true" ]]; then "java-functions-samples-secrets.txt" \ "java-firestore-samples-secrets.txt" \ "java-cts-v4-samples-secrets.txt" \ - "java-cloud-sql-samples-secrets.txt") + "java-cloud-sql-samples-secrets.txt" \ + "java-scc-samples-secrets.txt") # create secret dir mkdir -p "${KOKORO_GFILE_DIR}/secrets" diff --git a/appengine-java8/firebase-tictactoe/pom.xml b/appengine-java8/firebase-tictactoe/pom.xml index 36e235bbac7..a5b935a02cb 100644 --- a/appengine-java8/firebase-tictactoe/pom.xml +++ b/appengine-java8/firebase-tictactoe/pom.xml @@ -72,7 +72,7 @@ com.google.api-client google-api-client-appengine - 1.34.1 + 2.0.1 diff --git a/asset/pom.xml b/asset/pom.xml new file mode 100644 index 00000000000..295fc5cc73a --- /dev/null +++ b/asset/pom.xml @@ -0,0 +1,84 @@ + + + 4.0.0 + com.google.cloud + cloudasset-snippets + jar + Google Cloud Asset Inventory Snippets + https://github.com/googleapis/java-asset + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + 1.8 + 1.8 + UTF-8 + + + + + + + com.google.cloud + libraries-bom + 26.1.3 + pom + import + + + + + + + com.google.cloud + google-cloud-asset + + + + com.google.cloud + google-cloud-core + + + com.google.cloud + google-cloud-storage + test + + + com.google.cloud + google-cloud-bigquery + test + + + com.google.cloud + google-cloud-pubsub + test + + + com.google.cloud + google-cloud-resourcemanager + test + + + junit + junit + 4.13.2 + test + + + com.google.truth + truth + 1.1.3 + test + + + + + diff --git a/asset/src/main/java/com/example/asset/AnalyzeIamPolicyExample.java b/asset/src/main/java/com/example/asset/AnalyzeIamPolicyExample.java new file mode 100644 index 00000000000..1b35d131507 --- /dev/null +++ b/asset/src/main/java/com/example/asset/AnalyzeIamPolicyExample.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.asset; + +// [START asset_quickstart_analyze_iam_policy] +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.asset.v1.AnalyzeIamPolicyRequest; +import com.google.cloud.asset.v1.AnalyzeIamPolicyResponse; +import com.google.cloud.asset.v1.AssetServiceClient; +import com.google.cloud.asset.v1.IamPolicyAnalysisQuery; +import com.google.cloud.asset.v1.IamPolicyAnalysisQuery.Options; +import com.google.cloud.asset.v1.IamPolicyAnalysisQuery.ResourceSelector; +import java.io.IOException; + +public class AnalyzeIamPolicyExample { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String scope = "organizations/ORG_ID"; + String fullResourceName = "//cloudresourcemanager.googleapis.com/projects/PROJ_ID"; + analyzeIamPolicy(scope, fullResourceName); + } + + // Analyzes accessible IAM policies that match a request. + public static void analyzeIamPolicy(String scope, String fullResourceName) { + ResourceSelector resourceSelector = + ResourceSelector.newBuilder().setFullResourceName(fullResourceName).build(); + Options options = Options.newBuilder().setExpandGroups(true).setOutputGroupEdges(true).build(); + IamPolicyAnalysisQuery query = + IamPolicyAnalysisQuery.newBuilder() + .setScope(scope) + .setResourceSelector(resourceSelector) + .setOptions(options) + .build(); + AnalyzeIamPolicyRequest request = + AnalyzeIamPolicyRequest.newBuilder().setAnalysisQuery(query).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 (AssetServiceClient client = AssetServiceClient.create()) { + AnalyzeIamPolicyResponse response = client.analyzeIamPolicy(request); + System.out.println("Analyze completed successfully:\n" + response); + } catch (IOException e) { + System.out.println("Failed to create client:\n" + e.toString()); + } catch (ApiException e) { + System.out.println("Error during AnalyzeIamPolicy:\n" + e.toString()); + } + } +} +// [END asset_quickstart_analyze_iam_policy] diff --git a/asset/src/main/java/com/example/asset/AnalyzeIamPolicyLongrunningBigqueryExample.java b/asset/src/main/java/com/example/asset/AnalyzeIamPolicyLongrunningBigqueryExample.java new file mode 100644 index 00000000000..a5387e0a46c --- /dev/null +++ b/asset/src/main/java/com/example/asset/AnalyzeIamPolicyLongrunningBigqueryExample.java @@ -0,0 +1,86 @@ +/* + * 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.asset; + +// [START asset_quickstart_analyze_iam_policy_longrunning_bigquery] +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.asset.v1.AnalyzeIamPolicyLongrunningRequest; +import com.google.cloud.asset.v1.AssetServiceClient; +import com.google.cloud.asset.v1.IamPolicyAnalysisOutputConfig; +import com.google.cloud.asset.v1.IamPolicyAnalysisOutputConfig.BigQueryDestination; +import com.google.cloud.asset.v1.IamPolicyAnalysisQuery; +import com.google.cloud.asset.v1.IamPolicyAnalysisQuery.Options; +import com.google.cloud.asset.v1.IamPolicyAnalysisQuery.ResourceSelector; +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +public class AnalyzeIamPolicyLongrunningBigqueryExample { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String scope = "organizations/ORG_ID"; + String fullResourceName = "//cloudresourcemanager.googleapis.com/projects/PROJ_ID"; + String dataset = "projects/PROJ_ID/datasets/DATASET_ID"; + String tablePrefix = "TABLE_PREFIX"; + analyzeIamPolicyLongrunning(scope, fullResourceName, dataset, tablePrefix); + } + + // Analyzes accessible IAM policies that match a request. + public static void analyzeIamPolicyLongrunning( + String scope, String fullResourceName, String dataset, String tablePrefix) { + ResourceSelector resourceSelector = + ResourceSelector.newBuilder().setFullResourceName(fullResourceName).build(); + Options options = Options.newBuilder().setExpandGroups(true).setOutputGroupEdges(true).build(); + IamPolicyAnalysisQuery query = + IamPolicyAnalysisQuery.newBuilder() + .setScope(scope) + .setResourceSelector(resourceSelector) + .setOptions(options) + .build(); + + BigQueryDestination bigQueryDestination = + BigQueryDestination.newBuilder().setDataset(dataset).setTablePrefix(tablePrefix).build(); + IamPolicyAnalysisOutputConfig outputConfig = + IamPolicyAnalysisOutputConfig.newBuilder() + .setBigqueryDestination(bigQueryDestination) + .build(); + + AnalyzeIamPolicyLongrunningRequest request = + AnalyzeIamPolicyLongrunningRequest.newBuilder() + .setAnalysisQuery(query) + .setOutputConfig(outputConfig) + .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 (AssetServiceClient client = AssetServiceClient.create()) { + System.out.println( + "Analyze completed successfully:\n" + + client.analyzeIamPolicyLongrunningAsync(request).getMetadata().get()); + } catch (IOException e) { + System.out.println("Failed to create client:\n" + e.toString()); + } catch (InterruptedException e) { + System.out.println("Operation was interrupted:\n" + e.toString()); + } catch (ExecutionException e) { + System.out.println("Operation was aborted:\n" + e.toString()); + } catch (ApiException e) { + System.out.println("Error during AnalyzeIamPolicyLongrunning:\n" + e.toString()); + } + } +} +// [END asset_quickstart_analyze_iam_policy_longrunning_bigquery] diff --git a/asset/src/main/java/com/example/asset/AnalyzeIamPolicyLongrunningGcsExample.java b/asset/src/main/java/com/example/asset/AnalyzeIamPolicyLongrunningGcsExample.java new file mode 100644 index 00000000000..0784685077d --- /dev/null +++ b/asset/src/main/java/com/example/asset/AnalyzeIamPolicyLongrunningGcsExample.java @@ -0,0 +1,84 @@ +/* + * 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.asset; + +// [START asset_quickstart_analyze_iam_policy_longrunning_gcs] +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.asset.v1.AnalyzeIamPolicyLongrunningRequest; +import com.google.cloud.asset.v1.AssetServiceClient; +import com.google.cloud.asset.v1.IamPolicyAnalysisOutputConfig; +import com.google.cloud.asset.v1.IamPolicyAnalysisOutputConfig.GcsDestination; +import com.google.cloud.asset.v1.IamPolicyAnalysisQuery; +import com.google.cloud.asset.v1.IamPolicyAnalysisQuery.Options; +import com.google.cloud.asset.v1.IamPolicyAnalysisQuery.ResourceSelector; +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +public class AnalyzeIamPolicyLongrunningGcsExample { + + public static void main(String[] args) { + // TODO(developer): Replace these variables before running the sample. + String scope = "organizations/ORG_ID"; + String fullResourceName = "//cloudresourcemanager.googleapis.com/projects/PROJ_ID"; + String uri = "gs://BUCKET_NAME/OBJECT_NAME"; + analyzeIamPolicyLongrunning(scope, fullResourceName, uri); + } + + // Analyzes accessible IAM policies that match a request. + public static void analyzeIamPolicyLongrunning( + String scope, String fullResourceName, String uri) { + ResourceSelector resourceSelector = + ResourceSelector.newBuilder().setFullResourceName(fullResourceName).build(); + Options options = Options.newBuilder().setExpandGroups(true).setOutputGroupEdges(true).build(); + IamPolicyAnalysisQuery query = + IamPolicyAnalysisQuery.newBuilder() + .setScope(scope) + .setResourceSelector(resourceSelector) + .setOptions(options) + .build(); + + GcsDestination gcsDestination = GcsDestination.newBuilder().setUri(uri).build(); + IamPolicyAnalysisOutputConfig outputConfig = + IamPolicyAnalysisOutputConfig.newBuilder() + .setGcsDestination(GcsDestination.newBuilder().setUri(uri).build()) + .build(); + + AnalyzeIamPolicyLongrunningRequest request = + AnalyzeIamPolicyLongrunningRequest.newBuilder() + .setAnalysisQuery(query) + .setOutputConfig(outputConfig) + .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 (AssetServiceClient client = AssetServiceClient.create()) { + System.out.println( + "Analyze completed successfully:\n" + + client.analyzeIamPolicyLongrunningAsync(request).getMetadata().get()); + } catch (IOException e) { + System.out.println("Failed to create client:\n" + e.toString()); + } catch (InterruptedException e) { + System.out.println("Operation was interrupted:\n" + e.toString()); + } catch (ExecutionException e) { + System.out.println("Operation was aborted:\n" + e.toString()); + } catch (ApiException e) { + System.out.println("Error during AnalyzeIamPolicyLongrunning:\n" + e.toString()); + } + } +} +// [END asset_quickstart_analyze_iam_policy_longrunning_gcs] diff --git a/asset/src/main/java/com/example/asset/BatchGetAssetsHistoryExample.java b/asset/src/main/java/com/example/asset/BatchGetAssetsHistoryExample.java new file mode 100644 index 00000000000..e244bfe767c --- /dev/null +++ b/asset/src/main/java/com/example/asset/BatchGetAssetsHistoryExample.java @@ -0,0 +1,57 @@ +/* + * 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.asset; + +// [START asset_quickstart_batch_get_assets_history] +// Imports the Google Cloud client library + +import com.google.cloud.ServiceOptions; +import com.google.cloud.asset.v1.AssetServiceClient; +import com.google.cloud.asset.v1.BatchGetAssetsHistoryRequest; +import com.google.cloud.asset.v1.BatchGetAssetsHistoryResponse; +import com.google.cloud.asset.v1.ContentType; +import com.google.cloud.asset.v1.ProjectName; +import com.google.cloud.asset.v1.TimeWindow; +import java.util.Arrays; + +public class BatchGetAssetsHistoryExample { + + // Use the default project Id. + private static final String projectId = ServiceOptions.getDefaultProjectId(); + + // Export assets for a project. + // @param args path where the results will be exported to. + public static void main(String... args) throws Exception { + // Asset names, e.g.: "//storage.googleapis.com/[BUCKET_NAME]" + String[] assetNames = args[0].split(","); + try (AssetServiceClient client = AssetServiceClient.create()) { + ProjectName parent = ProjectName.of(projectId); + ContentType contentType = ContentType.CONTENT_TYPE_UNSPECIFIED; + TimeWindow readTimeWindow = TimeWindow.newBuilder().build(); + BatchGetAssetsHistoryRequest request = + BatchGetAssetsHistoryRequest.newBuilder() + .setParent(parent.toString()) + .addAllAssetNames(Arrays.asList(assetNames)) + .setContentType(contentType) + .setReadTimeWindow(readTimeWindow) + .build(); + BatchGetAssetsHistoryResponse response = client.batchGetAssetsHistory(request); + System.out.println(response); + } + } +} +// [END asset_quickstart_batch_get_assets_history] diff --git a/asset/src/main/java/com/example/asset/CreateFeedExample.java b/asset/src/main/java/com/example/asset/CreateFeedExample.java new file mode 100644 index 00000000000..ef28da1120d --- /dev/null +++ b/asset/src/main/java/com/example/asset/CreateFeedExample.java @@ -0,0 +1,66 @@ +/* + * 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.asset; + +// [START asset_quickstart_create_feed] +import com.google.cloud.asset.v1.AssetServiceClient; +import com.google.cloud.asset.v1.ContentType; +import com.google.cloud.asset.v1.CreateFeedRequest; +import com.google.cloud.asset.v1.Feed; +import com.google.cloud.asset.v1.FeedOutputConfig; +import com.google.cloud.asset.v1.ProjectName; +import com.google.cloud.asset.v1.PubsubDestination; +import java.io.IOException; +import java.util.Arrays; + +public class CreateFeedExample { + // Create a feed + public static void createFeed( + String[] assetNames, String feedId, String topic, String projectId, ContentType contentType) + throws IOException, IllegalArgumentException { + // String[] assetNames = {"MY_ASSET_NAME"} + // ContentType contentType = contentType + // String FeedId = "MY_FEED_ID" + // String topic = "projects/[PROJECT_ID]/topics/[TOPIC_NAME]" + // String projectID = "MY_PROJECT_ID" + Feed feed = + Feed.newBuilder() + .addAllAssetNames(Arrays.asList(assetNames)) + .setContentType(contentType) + .setFeedOutputConfig( + FeedOutputConfig.newBuilder() + .setPubsubDestination(PubsubDestination.newBuilder().setTopic(topic).build()) + .build()) + .build(); + CreateFeedRequest request = + CreateFeedRequest.newBuilder() + .setParent(String.format(ProjectName.of(projectId).toString())) + .setFeedId(feedId) + .setFeed(feed) + .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 (AssetServiceClient client = AssetServiceClient.create()) { + Feed response = client.createFeed(request); + System.out.println("Feed created successfully: " + response.getName()); + } catch (IOException | IllegalArgumentException e) { + System.out.println("Error during CreateFeed: \n" + e.toString()); + } + } +} +// [END asset_quickstart_create_feed] diff --git a/asset/src/main/java/com/example/asset/DeleteFeedExample.java b/asset/src/main/java/com/example/asset/DeleteFeedExample.java new file mode 100644 index 00000000000..162b4e2ab84 --- /dev/null +++ b/asset/src/main/java/com/example/asset/DeleteFeedExample.java @@ -0,0 +1,39 @@ +/* + * 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.asset; + +// [START asset_quickstart_delete_feed] +import com.google.cloud.asset.v1.AssetServiceClient; + +public class DeleteFeedExample { + + // Delete a feed with full feed name + public static void deleteFeed(String feedName) throws Exception { + // String feedName = "MY_FEED_NAME" + + // 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 (AssetServiceClient client = AssetServiceClient.create()) { + client.deleteFeed(feedName); + System.out.println("Feed deleted"); + } catch (Exception e) { + System.out.println("Error during DeleteFeed: \n" + e.toString()); + } + } +} +// [END asset_quickstart_delete_feed] diff --git a/asset/src/main/java/com/example/asset/ExportAssetsBigqueryExample.java b/asset/src/main/java/com/example/asset/ExportAssetsBigqueryExample.java new file mode 100644 index 00000000000..599ef50a431 --- /dev/null +++ b/asset/src/main/java/com/example/asset/ExportAssetsBigqueryExample.java @@ -0,0 +1,84 @@ +/* + * 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.asset; + +// [START asset_quickstart_export_assets_bigquery] +// Imports the Google Cloud client library + +import com.google.cloud.ServiceOptions; +import com.google.cloud.asset.v1.AssetServiceClient; +import com.google.cloud.asset.v1.BigQueryDestination; +import com.google.cloud.asset.v1.ContentType; +import com.google.cloud.asset.v1.ExportAssetsRequest; +import com.google.cloud.asset.v1.ExportAssetsResponse; +import com.google.cloud.asset.v1.OutputConfig; +import com.google.cloud.asset.v1.PartitionSpec; +import com.google.cloud.asset.v1.ProjectName; +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +public class ExportAssetsBigqueryExample { + + // Use the default project Id. + private static final String projectId = ServiceOptions.getDefaultProjectId(); + + // Export assets to BigQuery for a project. + public static void exportBigQuery( + String bigqueryDataset, String bigqueryTable, ContentType contentType, boolean isPerType) + throws IOException, IllegalArgumentException, InterruptedException, ExecutionException { + try (AssetServiceClient client = AssetServiceClient.create()) { + ProjectName parent = ProjectName.of(projectId); + OutputConfig outputConfig; + // Outputs to per-type BigQuery table. + if (isPerType) { + outputConfig = + OutputConfig.newBuilder() + .setBigqueryDestination( + BigQueryDestination.newBuilder() + .setDataset(bigqueryDataset) + .setTable(bigqueryTable) + .setForce(true) + .setSeparateTablesPerAssetType(true) + .setPartitionSpec( + PartitionSpec.newBuilder() + .setPartitionKey(PartitionSpec.PartitionKey.READ_TIME) + .build()) + .build()) + .build(); + } else { + outputConfig = + OutputConfig.newBuilder() + .setBigqueryDestination( + BigQueryDestination.newBuilder() + .setDataset(bigqueryDataset) + .setTable(bigqueryTable) + .setForce(true) + .build()) + .build(); + } + ExportAssetsRequest request = + ExportAssetsRequest.newBuilder() + .setParent(parent.toString()) + .setContentType(contentType) + .setOutputConfig(outputConfig) + .build(); + ExportAssetsResponse response = client.exportAssetsAsync(request).get(); + System.out.println(response); + } + } +} +// [END asset_quickstart_export_assets_bigquery] diff --git a/asset/src/main/java/com/example/asset/ExportAssetsExample.java b/asset/src/main/java/com/example/asset/ExportAssetsExample.java new file mode 100644 index 00000000000..8e31c6b2de5 --- /dev/null +++ b/asset/src/main/java/com/example/asset/ExportAssetsExample.java @@ -0,0 +1,59 @@ +/* + * 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.asset; + +// [START asset_quickstart_export_assets] +// Imports the Google Cloud client library + +import com.google.cloud.ServiceOptions; +import com.google.cloud.asset.v1.AssetServiceClient; +import com.google.cloud.asset.v1.ContentType; +import com.google.cloud.asset.v1.ExportAssetsRequest; +import com.google.cloud.asset.v1.ExportAssetsResponse; +import com.google.cloud.asset.v1.GcsDestination; +import com.google.cloud.asset.v1.OutputConfig; +import com.google.cloud.asset.v1.ProjectName; +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +public class ExportAssetsExample { + + // Use the default project Id. + private static final String projectId = ServiceOptions.getDefaultProjectId(); + + // Export assets for a project. + // @param exportPath where the results will be exported to. + public static void exportAssets(String exportPath, ContentType contentType) + throws IOException, IllegalArgumentException, InterruptedException, ExecutionException { + try (AssetServiceClient client = AssetServiceClient.create()) { + ProjectName parent = ProjectName.of(projectId); + OutputConfig outputConfig = + OutputConfig.newBuilder() + .setGcsDestination(GcsDestination.newBuilder().setUri(exportPath).build()) + .build(); + ExportAssetsRequest request = + ExportAssetsRequest.newBuilder() + .setParent(parent.toString()) + .setOutputConfig(outputConfig) + .setContentType(contentType) + .build(); + ExportAssetsResponse response = client.exportAssetsAsync(request).get(); + System.out.println(response); + } + } +} +// [END asset_quickstart_export_assets] diff --git a/asset/src/main/java/com/example/asset/GetFeedExample.java b/asset/src/main/java/com/example/asset/GetFeedExample.java new file mode 100644 index 00000000000..cc0b5a5f552 --- /dev/null +++ b/asset/src/main/java/com/example/asset/GetFeedExample.java @@ -0,0 +1,40 @@ +/* + * 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.asset; + +// [START asset_quickstart_get_feed] +import com.google.cloud.asset.v1.AssetServiceClient; +import com.google.cloud.asset.v1.Feed; + +public class GetFeedExample { + + // Get a feed with full feed name + public static void getFeed(String feedName) throws Exception { + // String feedName = "MY_FEED_NAME" + + // 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 (AssetServiceClient client = AssetServiceClient.create()) { + Feed feed = client.getFeed(feedName); + System.out.println("Get a feed: " + feedName); + } catch (Exception e) { + System.out.println("Error during GetFeed: \n" + e.toString()); + } + } +} +// [END asset_quickstart_get_feed] diff --git a/asset/src/main/java/com/example/asset/ListAssetsExample.java b/asset/src/main/java/com/example/asset/ListAssetsExample.java new file mode 100644 index 00000000000..43d2063bb6b --- /dev/null +++ b/asset/src/main/java/com/example/asset/ListAssetsExample.java @@ -0,0 +1,71 @@ +/* + * 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.asset; + +import com.google.cloud.asset.v1.AssetServiceClient; +import com.google.cloud.asset.v1.AssetServiceClient.ListAssetsPagedResponse; +import com.google.cloud.asset.v1.ContentType; +import com.google.cloud.asset.v1.ListAssetsRequest; +import com.google.cloud.asset.v1.ProjectName; +import java.io.IOException; +import java.util.Arrays; + +// [START asset_quickstart_list_assets] +// Imports the Google Cloud client library + +public class ListAssetsExample { + + public static void listAssets() throws IOException, IllegalArgumentException { + // The project id of the asset parent to list. + String projectId = "YOUR_PROJECT_ID"; + // The asset types to list. E.g., + // ["storage.googleapis.com/Bucket", "bigquery.googleapis.com/Table"]. + // See full list of supported asset types at + // https://cloud.google.com/asset-inventory/docs/supported-asset-types. + String[] assetTypes = {"YOUR_ASSET_TYPES_TO_LIST"}; + // The asset content type to list. E.g., ContentType.CONTENT_TYPE_UNSPECIFIED. + // See full list of content types at + // https://cloud.google.com/asset-inventory/docs/reference/rpc/google.cloud.asset.v1#contenttype + ContentType contentType = ContentType.CONTENT_TYPE_UNSPECIFIED; + listAssets(projectId, assetTypes, contentType); + } + + public static void listAssets(String projectId, String[] assetTypes, ContentType contentType) + throws IOException, IllegalArgumentException { + try (AssetServiceClient client = AssetServiceClient.create()) { + ProjectName parent = ProjectName.of(projectId); + + // Build initial ListAssetsRequest without setting page token. + ListAssetsRequest request = + ListAssetsRequest.newBuilder() + .setParent(parent.toString()) + .addAllAssetTypes(Arrays.asList(assetTypes)) + .setContentType(contentType) + .build(); + + // Repeatedly call ListAssets until page token is empty. + ListAssetsPagedResponse response = client.listAssets(request); + System.out.println(response); + while (!response.getNextPageToken().isEmpty()) { + request = request.toBuilder().setPageToken(response.getNextPageToken()).build(); + response = client.listAssets(request); + System.out.println(response); + } + } + } +} +// [END asset_quickstart_list_assets] diff --git a/asset/src/main/java/com/example/asset/ListFeedsExample.java b/asset/src/main/java/com/example/asset/ListFeedsExample.java new file mode 100644 index 00000000000..1a23535ca18 --- /dev/null +++ b/asset/src/main/java/com/example/asset/ListFeedsExample.java @@ -0,0 +1,41 @@ +/* + * 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.asset; + +// [START asset_quickstart_list_feeds] +import com.google.cloud.asset.v1.AssetServiceClient; +import com.google.cloud.asset.v1.ListFeedsResponse; +import com.google.cloud.asset.v1.ProjectName; + +public class ListFeedsExample { + // List feeds in a project. + public static void listFeeds(String projectId) throws Exception { + // String projectId = "MY_PROJECT_ID" + // String topic = "projects/[PROJECT_ID]/topics/[TOPIC_NAME]" + + // 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 (AssetServiceClient client = AssetServiceClient.create()) { + ListFeedsResponse response = client.listFeeds(ProjectName.of(projectId).toString()); + System.out.println("Listed feeds under: " + projectId); + } catch (Exception e) { + System.out.println("Error during ListFeeds: \n" + e.toString()); + } + } +} +// [END asset_quickstart_list_feeds] diff --git a/asset/src/main/java/com/example/asset/SearchAllIamPoliciesExample.java b/asset/src/main/java/com/example/asset/SearchAllIamPoliciesExample.java new file mode 100644 index 00000000000..202f85af29a --- /dev/null +++ b/asset/src/main/java/com/example/asset/SearchAllIamPoliciesExample.java @@ -0,0 +1,58 @@ +/* + * 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.asset; + +// [START asset_quickstart_search_all_iam_policies] +import com.google.api.gax.rpc.ApiException; +import com.google.api.gax.rpc.InvalidArgumentException; +import com.google.cloud.asset.v1.AssetServiceClient; +import com.google.cloud.asset.v1.AssetServiceClient.SearchAllIamPoliciesPagedResponse; +import com.google.cloud.asset.v1.SearchAllIamPoliciesRequest; +import java.io.IOException; + +public class SearchAllIamPoliciesExample { + + // Searches for all the iam policies within the given scope. + public static void searchAllIamPolicies(String scope, String query) { + // TODO(developer): Replace these variables before running the sample. + int pageSize = 0; + String pageToken = ""; + + SearchAllIamPoliciesRequest request = + SearchAllIamPoliciesRequest.newBuilder() + .setScope(scope) + .setQuery(query) + .setPageSize(pageSize) + .setPageToken(pageToken) + .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 (AssetServiceClient client = AssetServiceClient.create()) { + SearchAllIamPoliciesPagedResponse response = client.searchAllIamPolicies(request); + System.out.println("Search completed successfully:\n" + response.getPage().getValues()); + } catch (IOException e) { + System.out.println(String.format("Failed to create client:%n%s", e.toString())); + } catch (InvalidArgumentException e) { + System.out.println(String.format("Invalid request:%n%s", e.toString())); + } catch (ApiException e) { + System.out.println(String.format("Error during SearchAllIamPolicies:%n%s", e.toString())); + } + } +} +// [END asset_quickstart_search_all_iam_policies] diff --git a/asset/src/main/java/com/example/asset/SearchAllResourcesExample.java b/asset/src/main/java/com/example/asset/SearchAllResourcesExample.java new file mode 100644 index 00000000000..42896575405 --- /dev/null +++ b/asset/src/main/java/com/example/asset/SearchAllResourcesExample.java @@ -0,0 +1,63 @@ +/* + * 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.asset; + +// [START asset_quickstart_search_all_resources] +import com.google.api.gax.rpc.ApiException; +import com.google.api.gax.rpc.InvalidArgumentException; +import com.google.cloud.asset.v1.AssetServiceClient; +import com.google.cloud.asset.v1.AssetServiceClient.SearchAllResourcesPagedResponse; +import com.google.cloud.asset.v1.SearchAllResourcesRequest; +import java.io.IOException; +import java.util.Arrays; + +public class SearchAllResourcesExample { + + // Searches for all the resources within the given scope. + public static void searchAllResources(String scope, String query) { + // TODO(developer): Replace these variables before running the sample. + String[] assetTypes = {}; + int pageSize = 0; + String pageToken = ""; + String orderBy = ""; + + SearchAllResourcesRequest request = + SearchAllResourcesRequest.newBuilder() + .setScope(scope) + .setQuery(query) + .addAllAssetTypes(Arrays.asList(assetTypes)) + .setPageSize(pageSize) + .setPageToken(pageToken) + .setOrderBy(orderBy) + .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 (AssetServiceClient client = AssetServiceClient.create()) { + SearchAllResourcesPagedResponse response = client.searchAllResources(request); + System.out.println("Search completed successfully:\n" + response.getPage().getValues()); + } catch (IOException e) { + System.out.println(String.format("Failed to create client:%n%s", e.toString())); + } catch (InvalidArgumentException e) { + System.out.println(String.format("Invalid request:%n%s", e.toString())); + } catch (ApiException e) { + System.out.println(String.format("Error during SearchAllResources:%n%s", e.toString())); + } + } +} +// [END asset_quickstart_search_all_resources] diff --git a/asset/src/main/java/com/example/asset/UpdateFeedExample.java b/asset/src/main/java/com/example/asset/UpdateFeedExample.java new file mode 100644 index 00000000000..1719d5a15dc --- /dev/null +++ b/asset/src/main/java/com/example/asset/UpdateFeedExample.java @@ -0,0 +1,60 @@ +/* + * 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.asset; + +// [START asset_quickstart_update_feed] +import com.google.cloud.asset.v1.AssetServiceClient; +import com.google.cloud.asset.v1.Feed; +import com.google.cloud.asset.v1.FeedOutputConfig; +import com.google.cloud.asset.v1.PubsubDestination; +import com.google.cloud.asset.v1.UpdateFeedRequest; +import com.google.protobuf.FieldMask; + +public class UpdateFeedExample { + + // Update a feed + public static void updateFeed(String feedName, String topic) throws Exception { + // String feedName = "MY_FEED_NAME" + // String topic = "projects/[PROJECT_ID]/topics/[TOPIC_NAME]" + Feed feed = + Feed.newBuilder() + .setName(feedName) + .setFeedOutputConfig( + FeedOutputConfig.newBuilder() + .setPubsubDestination(PubsubDestination.newBuilder().setTopic(topic).build()) + .build()) + .build(); + UpdateFeedRequest request = + UpdateFeedRequest.newBuilder() + .setFeed(feed) + .setUpdateMask( + FieldMask.newBuilder() + .addPaths("feed_output_config.pubsub_destination.topic") + .build()) + .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 (AssetServiceClient client = AssetServiceClient.create()) { + Feed response = client.updateFeed(request); + System.out.println("Feed updated successfully: " + response.getName()); + } catch (Exception e) { + System.out.println("Error during UpdateFeed: \n" + e.toString()); + } + } +} +// [END asset_quickstart_update_feed] diff --git a/asset/src/test/java/com/example/asset/Analyze.java b/asset/src/test/java/com/example/asset/Analyze.java new file mode 100644 index 00000000000..16012d0d14b --- /dev/null +++ b/asset/src/test/java/com/example/asset/Analyze.java @@ -0,0 +1,124 @@ +/* + * 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.asset; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.bigquery.BigQuery; +import com.google.cloud.bigquery.BigQuery.DatasetDeleteOption; +import com.google.cloud.bigquery.BigQueryOptions; +import com.google.cloud.bigquery.DatasetId; +import com.google.cloud.bigquery.DatasetInfo; +import com.google.cloud.bigquery.testing.RemoteBigQueryHelper; +import com.google.cloud.storage.Blob; +import com.google.cloud.storage.BlobInfo; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.Storage.BlobListOption; +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.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for search samples. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class Analyze { + + private static final String projectId = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String scope = "projects/" + projectId; + private static final String fullResourceName = + "//cloudresourcemanager.googleapis.com/projects/" + projectId; + + private ByteArrayOutputStream bout; + private PrintStream out; + private PrintStream originalPrintStream; + + private static final void deleteObjects(String bucketName, String objectName) { + Storage storage = StorageOptions.getDefaultInstance().getService(); + Iterable blobs = + storage + .list( + bucketName, + BlobListOption.versions(true), + BlobListOption.currentDirectory(), + BlobListOption.prefix(objectName)) + .getValues(); + for (BlobInfo info : blobs) { + storage.delete(info.getBlobId()); + } + } + + @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); + } + + @Test + public void testAnalyzeIamPolicyExample() throws Exception { + AnalyzeIamPolicyExample.analyzeIamPolicy(scope, fullResourceName); + String got = bout.toString(); + assertThat(got).contains(fullResourceName); + } + + @Test + public void testAnalyzeIamPolicyLongrunningBigQueryExample() throws Exception { + String datasetName = RemoteBigQueryHelper.generateDatasetName(); + BigQuery bigquery = BigQueryOptions.getDefaultInstance().getService(); + if (bigquery.getDataset(datasetName) == null) { + bigquery.create(DatasetInfo.newBuilder(datasetName).build()); + } + + String dataset = "projects/" + projectId + "/datasets/" + datasetName; + String tablePrefix = "client_library_table"; + AnalyzeIamPolicyLongrunningBigqueryExample.analyzeIamPolicyLongrunning( + scope, fullResourceName, dataset, tablePrefix); + String got = bout.toString(); + assertThat(got).contains("output_config"); + + DatasetId datasetId = DatasetId.of(bigquery.getOptions().getProjectId(), datasetName); + bigquery.delete(datasetId, DatasetDeleteOption.deleteContents()); + } + + @Test + public void testAnalyzeIamPolicyLongrunningGcsExample() throws Exception { + // The developer needs to have bucket create permission or use an exsiting bucket. + String bucketName = "java-docs-samples-testing"; + String objectName = UUID.randomUUID().toString(); + + String uri = "gs://" + bucketName + "/" + objectName; + AnalyzeIamPolicyLongrunningGcsExample.analyzeIamPolicyLongrunning(scope, fullResourceName, uri); + String got = bout.toString(); + assertThat(got).contains("output_config"); + + deleteObjects(bucketName, objectName); + } +} diff --git a/asset/src/test/java/com/example/asset/ListAssets.java b/asset/src/test/java/com/example/asset/ListAssets.java new file mode 100644 index 00000000000..8422adcf3fe --- /dev/null +++ b/asset/src/test/java/com/example/asset/ListAssets.java @@ -0,0 +1,81 @@ +/* + * 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.asset; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.ServiceOptions; +import com.google.cloud.asset.v1.ContentType; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for list assets sample. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class ListAssets { + 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); + } + + @Test + public void testListAssetsExample() throws Exception { + // Use the default project Id (configure it by setting environment variable + // "GOOGLE_CLOUD_PROJECT"). + String projectId = ServiceOptions.getDefaultProjectId(); + String[] assetTypes = {"storage.googleapis.com/Bucket", "bigquery.googleapis.com/Table"}; + ContentType contentType = ContentType.CONTENT_TYPE_UNSPECIFIED; + ListAssetsExample.listAssets(projectId, assetTypes, contentType); + String got = bout.toString(); + if (!got.isEmpty()) { + assertThat(got).contains("asset"); + } + } + + @Test + public void testListAssetsRelationshipExample() throws Exception { + // Use the default project Id (configure it by setting environment variable + // "GOOGLE_CLOUD_PROJECT"). + String projectId = ServiceOptions.getDefaultProjectId(); + String[] assetTypes = {"compute.googleapis.com/Instance", "compute.googleapis.com/Disk"}; + ContentType contentType = ContentType.RELATIONSHIP; + ListAssetsExample.listAssets(projectId, assetTypes, contentType); + String got = bout.toString(); + if (!got.isEmpty()) { + assertThat(got).contains("asset"); + } + } +} diff --git a/asset/src/test/java/com/example/asset/QuickStartIT.java b/asset/src/test/java/com/example/asset/QuickStartIT.java new file mode 100644 index 00000000000..92c718e015d --- /dev/null +++ b/asset/src/test/java/com/example/asset/QuickStartIT.java @@ -0,0 +1,130 @@ +/* + * 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.asset; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.ServiceOptions; +import com.google.cloud.asset.v1.ContentType; +import com.google.cloud.bigquery.BigQuery; +import com.google.cloud.bigquery.BigQuery.DatasetDeleteOption; +import com.google.cloud.bigquery.BigQueryOptions; +import com.google.cloud.bigquery.Dataset; +import com.google.cloud.bigquery.DatasetId; +import com.google.cloud.bigquery.DatasetInfo; +import com.google.cloud.bigquery.testing.RemoteBigQueryHelper; +import com.google.cloud.storage.BlobInfo; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.Storage.BlobListOption; +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.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for quickstart sample. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class QuickStartIT { + private static final String bucketName = "java-docs-samples-testing"; + private static final String path = UUID.randomUUID().toString(); + private static final String datasetName = RemoteBigQueryHelper.generateDatasetName(); + private ByteArrayOutputStream bout; + private PrintStream out; + private PrintStream originalPrintStream; + private BigQuery bigquery; + + private static final void deleteObjects() { + Storage storage = StorageOptions.getDefaultInstance().getService(); + for (BlobInfo info : + storage + .list( + bucketName, + BlobListOption.versions(true), + BlobListOption.currentDirectory(), + BlobListOption.prefix(path + "/")) + .getValues()) { + storage.delete(info.getBlobId()); + } + } + + @Before + public void setUp() { + bigquery = BigQueryOptions.getDefaultInstance().getService(); + if (bigquery.getDataset(datasetName) == null) { + Dataset dataset = bigquery.create(DatasetInfo.newBuilder(datasetName).build()); + } + 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); + deleteObjects(); + DatasetId datasetId = DatasetId.of(bigquery.getOptions().getProjectId(), datasetName); + bigquery.delete(datasetId, DatasetDeleteOption.deleteContents()); + } + + @Test + public void testExportAssetExample() throws Exception { + String assetDumpPath = String.format("gs://%s/%s/my-assets-dump.txt", bucketName, path); + ExportAssetsExample.exportAssets(assetDumpPath, ContentType.RESOURCE); + String got = bout.toString(); + assertThat(got).contains(String.format("uri: \"%s\"", assetDumpPath)); + } + + @Test + public void testExportAssetBigqueryPerTypeExample() throws Exception { + String dataset = + String.format("projects/%s/datasets/%s", ServiceOptions.getDefaultProjectId(), datasetName); + String table = "java_test_per_type"; + ExportAssetsBigqueryExample.exportBigQuery( + dataset, table, ContentType.RESOURCE, /*perType*/ true); + String got = bout.toString(); + assertThat(got).contains(String.format("dataset: \"%s\"", dataset)); + } + + @Test + public void testExportAssetBigqueryExample() throws Exception { + String dataset = + String.format("projects/%s/datasets/%s", ServiceOptions.getDefaultProjectId(), datasetName); + String table = "java_test"; + ExportAssetsBigqueryExample.exportBigQuery( + dataset, table, ContentType.RESOURCE, /*perType*/ false); + String got = bout.toString(); + assertThat(got).contains(String.format("dataset: \"%s\"", dataset)); + } + + @Test + public void testBatchGetAssetsHistory() throws Exception { + String bucketAssetName = String.format("//storage.googleapis.com/%s", bucketName); + BatchGetAssetsHistoryExample.main(bucketAssetName); + String got = bout.toString(); + if (!got.isEmpty()) { + assertThat(got).contains(bucketAssetName); + } + } +} diff --git a/asset/src/test/java/com/example/asset/RealTimeFeed.java b/asset/src/test/java/com/example/asset/RealTimeFeed.java new file mode 100644 index 00000000000..c63e2e2e809 --- /dev/null +++ b/asset/src/test/java/com/example/asset/RealTimeFeed.java @@ -0,0 +1,137 @@ +/* + * 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.asset; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.asset.v1.ContentType; +import com.google.cloud.pubsub.v1.TopicAdminClient; +import com.google.cloud.resourcemanager.ProjectInfo; +import com.google.cloud.resourcemanager.ResourceManager; +import com.google.cloud.resourcemanager.ResourceManagerOptions; +import com.google.pubsub.v1.ProjectTopicName; +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.FixMethodOrder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.junit.runners.MethodSorters; + +/** Tests for real time feed sample. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class RealTimeFeed { + private static final String topicId = "topicId"; + private static final String feedId = UUID.randomUUID().toString(); + private static final String projectId = System.getenv("GOOGLE_CLOUD_PROJECT"); + private final String projectNumber = getProjectNumber(projectId); + private final String feedName = String.format("projects/%s/feeds/%s", projectNumber, feedId); + private final String[] assetNames = {UUID.randomUUID().toString()}; + private static final ProjectTopicName topicName = ProjectTopicName.of(projectId, topicId); + private ByteArrayOutputStream bout; + private PrintStream out; + private PrintStream originalPrintStream; + + private String getProjectNumber(String projectId) { + ResourceManager resourceManager = ResourceManagerOptions.getDefaultInstance().getService(); + ProjectInfo project = resourceManager.get(projectId); + return Long.toString(project.getProjectNumber()); + } + + @BeforeClass + public static void createTopic() throws Exception { + TopicAdminClient topicAdminClient = TopicAdminClient.create(); + topicAdminClient.createTopic(topicName); + } + + @AfterClass + public static void deleteTopic() throws Exception { + TopicAdminClient topicAdminClient = TopicAdminClient.create(); + topicAdminClient.deleteTopic(topicName); + } + + @Before + public void beforeTest() { + 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 test1CreateFeedExample() throws Exception { + CreateFeedExample.createFeed( + assetNames, feedId, topicName.toString(), projectId, ContentType.RESOURCE); + String got = bout.toString(); + assertThat(got).contains("Feed created successfully: " + feedName); + } + + @Test + public void test1CreateFeedRelationshipExample() throws Exception { + CreateFeedExample.createFeed( + assetNames, + feedId + "relationship", + topicName.toString(), + projectId, + ContentType.RELATIONSHIP); + String got = bout.toString(); + assertThat(got).contains("Feed created successfully: " + feedName); + } + + @Test + public void test2GetFeedExample() throws Exception { + GetFeedExample.getFeed(feedName); + String got = bout.toString(); + assertThat(got).contains("Get a feed: " + feedName); + } + + @Test + public void test3ListFeedsExample() throws Exception { + ListFeedsExample.listFeeds(projectId); + String got = bout.toString(); + assertThat(got).contains("Listed feeds under: " + projectId); + } + + @Test + public void test4UpdateFeedExample() throws Exception { + UpdateFeedExample.updateFeed(feedName, topicName.toString()); + String got = bout.toString(); + assertThat(got).contains("Feed updated successfully: " + feedName); + } + + @Test + public void test5DeleteFeedExample() throws Exception { + DeleteFeedExample.deleteFeed(feedName); + DeleteFeedExample.deleteFeed(feedName + "relationship"); + String got = bout.toString(); + assertThat(got).contains("Feed deleted"); + } +} diff --git a/asset/src/test/java/com/example/asset/Search.java b/asset/src/test/java/com/example/asset/Search.java new file mode 100644 index 00000000000..4e558571166 --- /dev/null +++ b/asset/src/test/java/com/example/asset/Search.java @@ -0,0 +1,87 @@ +/* + * 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.asset; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.bigquery.BigQuery; +import com.google.cloud.bigquery.BigQuery.DatasetDeleteOption; +import com.google.cloud.bigquery.BigQueryOptions; +import com.google.cloud.bigquery.DatasetId; +import com.google.cloud.bigquery.DatasetInfo; +import com.google.cloud.bigquery.testing.RemoteBigQueryHelper; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for search samples. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class Search { + + private static final String projectId = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String datasetName = RemoteBigQueryHelper.generateDatasetName(); + private ByteArrayOutputStream bout; + private PrintStream out; + private PrintStream originalPrintStream; + private BigQuery bigquery; + + @Before + public void setUp() { + bigquery = BigQueryOptions.getDefaultInstance().getService(); + if (bigquery.getDataset(datasetName) == null) { + bigquery.create(DatasetInfo.newBuilder(datasetName).build()); + } + 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); + DatasetId datasetId = DatasetId.of(bigquery.getOptions().getProjectId(), datasetName); + bigquery.delete(datasetId, DatasetDeleteOption.deleteContents()); + } + + @Test + public void testSearchAllResourcesExample() throws Exception { + // Wait 10 seconds to let dataset creation event go to CAI + Thread.sleep(10000); + String scope = "projects/" + projectId; + String query = "name:" + datasetName; + SearchAllResourcesExample.searchAllResources(scope, query); + String got = bout.toString(); + assertThat(got).contains(datasetName); + } + + @Test + public void testSearchAllIamPoliciesExample() throws Exception { + String scope = "projects/" + projectId; + String query = "policy:roles/owner"; + SearchAllIamPoliciesExample.searchAllIamPolicies(scope, query); + String got = bout.toString(); + assertThat(got).contains("roles/owner"); + } +} diff --git a/bigtable/spark/README.md b/bigtable/spark/README.md index 6f90897e524..006d7cd621b 100644 --- a/bigtable/spark/README.md +++ b/bigtable/spark/README.md @@ -22,7 +22,7 @@ Apache Spark provides DataSource API for external systems to plug into as data s 1. [sbt](https://www.scala-sbt.org/) installed. -1. [Apache Spark](https://spark.apache.org/) installed. Download Spark built for Scala 2.11. This sample uses Spark 2.4.7 and Scala 2.11.2. +1. [Apache Spark](https://spark.apache.org/) installed. Download Spark built for Scala 2.11. This sample uses Spark 2.4.8 and Scala 2.11.2. 1. A basic familiarity with [Apache Spark](https://spark.apache.org/) and [Scala](https://www.scala-lang.org/). @@ -54,7 +54,7 @@ Instructions for running the emulator can be found [here](https://cloud.google.c Set the following environment variables. ``` -SPARK_HOME=/PATH/TO/spark-2.4.7-bin-hadoop2.7 +SPARK_HOME=/PATH/TO/spark-2.4.8-bin-hadoop2.7 BIGTABLE_SPARK_PROJECT_ID=your-project-id BIGTABLE_SPARK_INSTANCE_ID=your-instance-id @@ -164,7 +164,7 @@ Turn off the emulator as described [here](https://cloud.google.com/bigtable/docs Set the following environment variables: ``` -SPARK_HOME=/PATH/TO/spark-2.4.7-bin-hadoop2.7 +SPARK_HOME=/PATH/TO/spark-2.4.8-bin-hadoop2.7 BIGTABLE_SPARK_PROJECT_ID=your-project-id BIGTABLE_SPARK_INSTANCE_ID=your-instance-id diff --git a/contact-center-insights/pom.xml b/contact-center-insights/pom.xml new file mode 100644 index 00000000000..3ae7e6e5010 --- /dev/null +++ b/contact-center-insights/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + com.example.contactcenterinsights + contact-center-insights-snippets + jar + Google CCAI Insights Snippets + https://github.com/GoogleCloudPlatform/java-docs-samples/tree/main/contact-center-insights + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + 1.8 + 1.8 + UTF-8 + + + + + + com.google.cloud + google-cloud-contact-center-insights + 2.3.9 + + + + junit + junit + 4.13.2 + test + + + com.google.truth + truth + 1.1.3 + test + + + com.google.cloud + google-cloud-bigquery + 2.17.1 + test + + + com.google.cloud + google-cloud-pubsub + 1.120.20 + test + + + diff --git a/contact-center-insights/src/main/java/com/example/contactcenterinsights/CreateAnalysis.java b/contact-center-insights/src/main/java/com/example/contactcenterinsights/CreateAnalysis.java new file mode 100644 index 00000000000..0b4293b921d --- /dev/null +++ b/contact-center-insights/src/main/java/com/example/contactcenterinsights/CreateAnalysis.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.contactcenterinsights; + +// [START contactcenterinsights_create_analysis] + +import com.google.cloud.contactcenterinsights.v1.Analysis; +import com.google.cloud.contactcenterinsights.v1.ContactCenterInsightsClient; +import java.io.IOException; + +public class CreateAnalysis { + + public static void main(String[] args) throws Exception, IOException { + // TODO(developer): Replace this variable before running the sample. + String conversationName = + "projects/my_project_id/locations/us-central1/conversations/my_conversation_id"; + + createAnalysis(conversationName); + } + + public static Analysis createAnalysis(String conversationName) throws Exception, 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 (ContactCenterInsightsClient client = ContactCenterInsightsClient.create()) { + // Construct an analysis. + Analysis analysis = Analysis.newBuilder().build(); + + // Call the Insights client to create an analysis. + Analysis response = client.createAnalysisAsync(conversationName, analysis).get(); + System.out.printf("Created %s%n", response.getName()); + return response; + } + } +} + +// [END contactcenterinsights_create_analysis] diff --git a/contact-center-insights/src/main/java/com/example/contactcenterinsights/CreateConversation.java b/contact-center-insights/src/main/java/com/example/contactcenterinsights/CreateConversation.java new file mode 100644 index 00000000000..cd03bd1535e --- /dev/null +++ b/contact-center-insights/src/main/java/com/example/contactcenterinsights/CreateConversation.java @@ -0,0 +1,78 @@ +/* + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.contactcenterinsights; + +// [START contactcenterinsights_create_conversation] + +import com.google.cloud.contactcenterinsights.v1.ContactCenterInsightsClient; +import com.google.cloud.contactcenterinsights.v1.Conversation; +import com.google.cloud.contactcenterinsights.v1.ConversationDataSource; +import com.google.cloud.contactcenterinsights.v1.CreateConversationRequest; +import com.google.cloud.contactcenterinsights.v1.GcsSource; +import com.google.cloud.contactcenterinsights.v1.LocationName; +import java.io.IOException; + +public class CreateConversation { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my_project_id"; + String transcriptUri = "gs://cloud-samples-data/ccai/chat_sample.json"; + String audioUri = "gs://cloud-samples-data/ccai/voice_6912.txt"; + + createConversation(projectId, transcriptUri, audioUri); + } + + public static Conversation createConversation( + String projectId, String transcriptUri, String audioUri) 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 (ContactCenterInsightsClient client = ContactCenterInsightsClient.create()) { + // Construct a parent resource. + LocationName parent = LocationName.of(projectId, "us-central1"); + + // Construct a conversation. + Conversation conversation = + Conversation.newBuilder() + .setDataSource( + ConversationDataSource.newBuilder() + .setGcsSource( + GcsSource.newBuilder() + .setTranscriptUri(transcriptUri) + .setAudioUri(audioUri) + .build()) + .build()) + .setMedium(Conversation.Medium.CHAT) + .build(); + + // Construct a request. + CreateConversationRequest request = + CreateConversationRequest.newBuilder() + .setParent(parent.toString()) + .setConversation(conversation) + .build(); + + // Call the Insights client to create a conversation. + Conversation response = client.createConversation(request); + System.out.printf("Created %s%n", response.getName()); + return response; + } + } +} + +// [END contactcenterinsights_create_conversation] diff --git a/contact-center-insights/src/main/java/com/example/contactcenterinsights/CreateConversationWithTtl.java b/contact-center-insights/src/main/java/com/example/contactcenterinsights/CreateConversationWithTtl.java new file mode 100644 index 00000000000..55649165c10 --- /dev/null +++ b/contact-center-insights/src/main/java/com/example/contactcenterinsights/CreateConversationWithTtl.java @@ -0,0 +1,80 @@ +/* + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.contactcenterinsights; + +// [START contactcenterinsights_create_conversation_with_ttl] + +import com.google.cloud.contactcenterinsights.v1.ContactCenterInsightsClient; +import com.google.cloud.contactcenterinsights.v1.Conversation; +import com.google.cloud.contactcenterinsights.v1.ConversationDataSource; +import com.google.cloud.contactcenterinsights.v1.CreateConversationRequest; +import com.google.cloud.contactcenterinsights.v1.GcsSource; +import com.google.cloud.contactcenterinsights.v1.LocationName; +import com.google.protobuf.Duration; +import java.io.IOException; + +public class CreateConversationWithTtl { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my_project_id"; + String transcriptUri = "gs://cloud-samples-data/ccai/chat_sample.json"; + String audioUri = "gs://cloud-samples-data/ccai/voice_6912.txt"; + + createConversationWithTtl(projectId, transcriptUri, audioUri); + } + + public static Conversation createConversationWithTtl( + String projectId, String transcriptUri, String audioUri) 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 (ContactCenterInsightsClient client = ContactCenterInsightsClient.create()) { + // Construct a parent resource. + LocationName parent = LocationName.of(projectId, "us-central1"); + + // Construct a conversation. + Conversation conversation = + Conversation.newBuilder() + .setDataSource( + ConversationDataSource.newBuilder() + .setGcsSource( + GcsSource.newBuilder() + .setTranscriptUri(transcriptUri) + .setAudioUri(audioUri) + .build()) + .build()) + .setMedium(Conversation.Medium.CHAT) + .setTtl(Duration.newBuilder().setSeconds(86400).build()) + .build(); + + // Construct a request. + CreateConversationRequest request = + CreateConversationRequest.newBuilder() + .setParent(parent.toString()) + .setConversation(conversation) + .build(); + + // Call the Insights client to create a conversation. + Conversation response = client.createConversation(request); + System.out.printf("Created %s%n", response.getName()); + return response; + } + } +} + +// [END contactcenterinsights_create_conversation_with_ttl] diff --git a/contact-center-insights/src/main/java/com/example/contactcenterinsights/CreateIssueModel.java b/contact-center-insights/src/main/java/com/example/contactcenterinsights/CreateIssueModel.java new file mode 100644 index 00000000000..0a3612b0fb6 --- /dev/null +++ b/contact-center-insights/src/main/java/com/example/contactcenterinsights/CreateIssueModel.java @@ -0,0 +1,59 @@ +/* + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.contactcenterinsights; + +// [START contactcenterinsights_create_issue_model] + +import com.google.cloud.contactcenterinsights.v1.ContactCenterInsightsClient; +import com.google.cloud.contactcenterinsights.v1.IssueModel; +import com.google.cloud.contactcenterinsights.v1.LocationName; +import java.io.IOException; + +public class CreateIssueModel { + + public static void main(String[] args) throws Exception, IOException { + // TODO(developer): Replace this variable before running the sample. + String projectId = "my_project_id"; + + createIssueModel(projectId); + } + + public static IssueModel createIssueModel(String projectId) throws Exception, 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 (ContactCenterInsightsClient client = ContactCenterInsightsClient.create()) { + // Construct a parent resource. + LocationName parent = LocationName.of(projectId, "us-central1"); + + // Construct an issue model. + IssueModel issueModel = + IssueModel.newBuilder() + .setDisplayName("my-model") + .setInputDataConfig( + IssueModel.InputDataConfig.newBuilder().setFilter("medium=\"CHAT\"").build()) + .build(); + + // Call the Insights client to create an issue model. + IssueModel response = client.createIssueModelAsync(parent, issueModel).get(); + System.out.printf("Created %s%n", response.getName()); + return response; + } + } +} + +// [END contactcenterinsights_create_issue_model] diff --git a/contact-center-insights/src/main/java/com/example/contactcenterinsights/CreatePhraseMatcherAllOf.java b/contact-center-insights/src/main/java/com/example/contactcenterinsights/CreatePhraseMatcherAllOf.java new file mode 100644 index 00000000000..ec8d75a3778 --- /dev/null +++ b/contact-center-insights/src/main/java/com/example/contactcenterinsights/CreatePhraseMatcherAllOf.java @@ -0,0 +1,96 @@ +/* + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.contactcenterinsights; + +// [START contactcenterinsights_create_phrase_matcher_all_of] + +import com.google.cloud.contactcenterinsights.v1.ContactCenterInsightsClient; +import com.google.cloud.contactcenterinsights.v1.ExactMatchConfig; +import com.google.cloud.contactcenterinsights.v1.LocationName; +import com.google.cloud.contactcenterinsights.v1.PhraseMatchRule; +import com.google.cloud.contactcenterinsights.v1.PhraseMatchRuleConfig; +import com.google.cloud.contactcenterinsights.v1.PhraseMatchRuleGroup; +import com.google.cloud.contactcenterinsights.v1.PhraseMatcher; +import java.io.IOException; + +public class CreatePhraseMatcherAllOf { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace this variable before running the sample. + String projectId = "my_project_id"; + + createPhraseMatcherAllOf(projectId); + } + + public static PhraseMatcher createPhraseMatcherAllOf(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 (ContactCenterInsightsClient client = ContactCenterInsightsClient.create()) { + // Construct a phrase matcher that matches all of its rule groups. + PhraseMatcher.Builder phraseMatcher = + PhraseMatcher.newBuilder() + .setDisplayName("NON_SHIPPING_PHONE_SERVICE") + .setTypeValue(1) + .setActive(true); + + // Construct a rule group to match the word "PHONE" or "CELLPHONE", ignoring case sensitivity. + PhraseMatchRuleGroup.Builder ruleGroup1 = PhraseMatchRuleGroup.newBuilder().setTypeValue(2); + + String[] words1 = {"PHONE", "CELLPHONE"}; + for (String w : words1) { + PhraseMatchRule.Builder rule = + PhraseMatchRule.newBuilder() + .setQuery(w) + .setConfig( + PhraseMatchRuleConfig.newBuilder() + .setExactMatchConfig(ExactMatchConfig.newBuilder().build()) + .build()); + ruleGroup1.addPhraseMatchRules(rule.build()); + } + phraseMatcher.addPhraseMatchRuleGroups(ruleGroup1.build()); + + // Construct another rule group to not match the word "SHIPPING" or "DELIVERY", + // ignoring case sensitivity. + PhraseMatchRuleGroup.Builder ruleGroup2 = PhraseMatchRuleGroup.newBuilder().setTypeValue(1); + + String[] words2 = {"SHIPPING", "DELIVERY"}; + for (String w : words2) { + PhraseMatchRule.Builder rule = + PhraseMatchRule.newBuilder() + .setQuery(w) + .setNegated(true) + .setConfig( + PhraseMatchRuleConfig.newBuilder() + .setExactMatchConfig(ExactMatchConfig.newBuilder().build()) + .build()); + ruleGroup2.addPhraseMatchRules(rule.build()); + } + phraseMatcher.addPhraseMatchRuleGroups(ruleGroup2.build()); + + // Construct a parent resource. + LocationName parent = LocationName.of(projectId, "us-central1"); + + // Call the Insights client to create a phrase matcher. + PhraseMatcher response = client.createPhraseMatcher(parent, phraseMatcher.build()); + System.out.printf("Created %s%n", response.getName()); + return response; + } + } +} + +// [END contactcenterinsights_create_phrase_matcher_all_of] diff --git a/contact-center-insights/src/main/java/com/example/contactcenterinsights/CreatePhraseMatcherAnyOf.java b/contact-center-insights/src/main/java/com/example/contactcenterinsights/CreatePhraseMatcherAnyOf.java new file mode 100644 index 00000000000..c1d0bcf3953 --- /dev/null +++ b/contact-center-insights/src/main/java/com/example/contactcenterinsights/CreatePhraseMatcherAnyOf.java @@ -0,0 +1,78 @@ +/* + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.contactcenterinsights; + +// [START contactcenterinsights_create_phrase_matcher_any_of] + +import com.google.cloud.contactcenterinsights.v1.ContactCenterInsightsClient; +import com.google.cloud.contactcenterinsights.v1.ExactMatchConfig; +import com.google.cloud.contactcenterinsights.v1.LocationName; +import com.google.cloud.contactcenterinsights.v1.PhraseMatchRule; +import com.google.cloud.contactcenterinsights.v1.PhraseMatchRuleConfig; +import com.google.cloud.contactcenterinsights.v1.PhraseMatchRuleGroup; +import com.google.cloud.contactcenterinsights.v1.PhraseMatcher; +import java.io.IOException; + +public class CreatePhraseMatcherAnyOf { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace this variable before running the sample. + String projectId = "my_project_id"; + + createPhraseMatcherAnyOf(projectId); + } + + public static PhraseMatcher createPhraseMatcherAnyOf(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 (ContactCenterInsightsClient client = ContactCenterInsightsClient.create()) { + // Construct a phrase matcher that matches any of its rule groups. + PhraseMatcher.Builder phraseMatcher = + PhraseMatcher.newBuilder() + .setDisplayName("PHONE_SERVICE") + .setTypeValue(2) + .setActive(true); + + // Construct a rule group to match the word "PHONE" or "CELLPHONE", ignoring case sensitivity. + PhraseMatchRuleGroup.Builder ruleGroup = PhraseMatchRuleGroup.newBuilder().setTypeValue(2); + + String[] words = {"PHONE", "CELLPHONE"}; + for (String w : words) { + PhraseMatchRule.Builder rule = + PhraseMatchRule.newBuilder() + .setQuery(w) + .setConfig( + PhraseMatchRuleConfig.newBuilder() + .setExactMatchConfig(ExactMatchConfig.newBuilder().build()) + .build()); + ruleGroup.addPhraseMatchRules(rule.build()); + } + phraseMatcher.addPhraseMatchRuleGroups(ruleGroup.build()); + + // Construct a parent resource. + LocationName parent = LocationName.of(projectId, "us-central1"); + + // Call the Insights client to create a phrase matcher. + PhraseMatcher response = client.createPhraseMatcher(parent, phraseMatcher.build()); + System.out.printf("Created %s%n", response.getName()); + return response; + } + } +} + +// [END contactcenterinsights_create_phrase_matcher_any_of] diff --git a/contact-center-insights/src/main/java/com/example/contactcenterinsights/EnablePubSubNotifications.java b/contact-center-insights/src/main/java/com/example/contactcenterinsights/EnablePubSubNotifications.java new file mode 100644 index 00000000000..6518985347d --- /dev/null +++ b/contact-center-insights/src/main/java/com/example/contactcenterinsights/EnablePubSubNotifications.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.contactcenterinsights; + +// [START contactcenterinsights_enable_pubsub_notifications] + +import com.google.cloud.contactcenterinsights.v1.ContactCenterInsightsClient; +import com.google.cloud.contactcenterinsights.v1.Settings; +import com.google.cloud.contactcenterinsights.v1.SettingsName; +import com.google.protobuf.FieldMask; +import java.io.IOException; + +public class EnablePubSubNotifications { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my_project_id"; + String topicCreateConversation = "projects/my_project_id/topics/my_topic_id"; + String topicCreateAnalysis = "projects/my_project_id/topics/my_other_topic_id"; + + enablePubSubNotifications(projectId, topicCreateConversation, topicCreateAnalysis); + } + + public static void enablePubSubNotifications( + String projectId, String topicCreateConversation, String topicCreateAnalysis) + 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 (ContactCenterInsightsClient client = ContactCenterInsightsClient.create()) { + // Construct a settings resource. + SettingsName name = SettingsName.of(projectId, "us-central1"); + Settings settings = + Settings.newBuilder() + .setName(name.toString()) + .putPubsubNotificationSettings("create-conversation", topicCreateConversation) + .putPubsubNotificationSettings("create-analysis", topicCreateAnalysis) + .build(); + + // Construct an update mask. + FieldMask updateMask = + FieldMask.newBuilder().addPaths("pubsub_notification_settings").build(); + + // Call the Insights client to enable Pub/Sub notifications. + Settings response = client.updateSettings(settings, updateMask); + System.out.printf("Enabled Pub/Sub notifications"); + } + } +} + +// [END contactcenterinsights_enable_pubsub_notifications] diff --git a/contact-center-insights/src/main/java/com/example/contactcenterinsights/ExportToBigquery.java b/contact-center-insights/src/main/java/com/example/contactcenterinsights/ExportToBigquery.java new file mode 100644 index 00000000000..c5e8de145dc --- /dev/null +++ b/contact-center-insights/src/main/java/com/example/contactcenterinsights/ExportToBigquery.java @@ -0,0 +1,90 @@ +/* + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.contactcenterinsights; + +// [START contactcenterinsights_export_to_bigquery] + +import com.google.api.gax.longrunning.OperationTimedPollAlgorithm; +import com.google.api.gax.retrying.RetrySettings; +import com.google.cloud.contactcenterinsights.v1.ContactCenterInsightsClient; +import com.google.cloud.contactcenterinsights.v1.ContactCenterInsightsSettings; +import com.google.cloud.contactcenterinsights.v1.ExportInsightsDataRequest; +import com.google.cloud.contactcenterinsights.v1.ExportInsightsDataResponse; +import com.google.cloud.contactcenterinsights.v1.LocationName; +import java.io.IOException; +import org.threeten.bp.Duration; + +public class ExportToBigquery { + + public static void main(String[] args) throws Exception, IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my_project_id"; + String bigqueryProjectId = "my_bigquery_project_id"; + String bigqueryDataset = "my_bigquery_dataset"; + String bigqueryTable = "my_bigquery_table"; + + exportToBigquery(projectId, bigqueryProjectId, bigqueryDataset, bigqueryTable); + } + + public static void exportToBigquery( + String projectId, String bigqueryProjectId, String bigqueryDataset, String bigqueryTable) + throws Exception, IOException { + // Set the operation total polling timeout to 24 hours instead of the 5-minute default. + // Other values are copied from the default values of {@link ContactCenterInsightsStubSettings}. + ContactCenterInsightsSettings.Builder clientSettings = + ContactCenterInsightsSettings.newBuilder(); + clientSettings + .exportInsightsDataOperationSettings() + .setPollingAlgorithm( + OperationTimedPollAlgorithm.create( + RetrySettings.newBuilder() + .setInitialRetryDelay(Duration.ofMillis(5000L)) + .setRetryDelayMultiplier(1.5) + .setMaxRetryDelay(Duration.ofMillis(45000L)) + .setInitialRpcTimeout(Duration.ZERO) + .setRpcTimeoutMultiplier(1.0) + .setMaxRpcTimeout(Duration.ZERO) + .setTotalTimeout(Duration.ofHours(24L)) + .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 (ContactCenterInsightsClient client = + ContactCenterInsightsClient.create(clientSettings.build())) { + // Construct an export request. + LocationName parent = LocationName.of(projectId, "us-central1"); + ExportInsightsDataRequest request = + ExportInsightsDataRequest.newBuilder() + .setParent(parent.toString()) + .setBigQueryDestination( + ExportInsightsDataRequest.BigQueryDestination.newBuilder() + .setProjectId(bigqueryProjectId) + .setDataset(bigqueryDataset) + .setTable(bigqueryTable) + .build()) + .setFilter("agent_id=\"007\"") + .build(); + + // Call the Insights client to export data to BigQuery. + ExportInsightsDataResponse response = client.exportInsightsDataAsync(request).get(); + System.out.printf("Exported data to BigQuery"); + } + } +} + +// [END contactcenterinsights_export_to_bigquery] diff --git a/contact-center-insights/src/main/java/com/example/contactcenterinsights/GetOperation.java b/contact-center-insights/src/main/java/com/example/contactcenterinsights/GetOperation.java new file mode 100644 index 00000000000..19721bca9b0 --- /dev/null +++ b/contact-center-insights/src/main/java/com/example/contactcenterinsights/GetOperation.java @@ -0,0 +1,49 @@ +/* + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.contactcenterinsights; + +// [START contactcenterinsights_get_operation] + +import com.google.cloud.contactcenterinsights.v1.ContactCenterInsightsClient; +import com.google.longrunning.Operation; +import com.google.longrunning.OperationsClient; +import java.io.IOException; + +public class GetOperation { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace this variable before running the sample. + String operationName = "projects/my_project_id/locations/us-central1/operations/12345"; + + getOperation(operationName); + } + + public static Operation getOperation(String operationName) 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 (ContactCenterInsightsClient client = ContactCenterInsightsClient.create()) { + OperationsClient operationsClient = client.getOperationsClient(); + Operation operation = operationsClient.getOperation(operationName); + + System.out.printf("Got operation %s%n", operation.getName()); + return operation; + } + } +} + +// [END contactcenterinsights_get_operation] diff --git a/contact-center-insights/src/main/java/com/example/contactcenterinsights/SetProjectTtl.java b/contact-center-insights/src/main/java/com/example/contactcenterinsights/SetProjectTtl.java new file mode 100644 index 00000000000..9e4b2a732eb --- /dev/null +++ b/contact-center-insights/src/main/java/com/example/contactcenterinsights/SetProjectTtl.java @@ -0,0 +1,60 @@ +/* + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.contactcenterinsights; + +// [START contactcenterinsights_set_project_ttl] + +import com.google.cloud.contactcenterinsights.v1.ContactCenterInsightsClient; +import com.google.cloud.contactcenterinsights.v1.Settings; +import com.google.cloud.contactcenterinsights.v1.SettingsName; +import com.google.protobuf.Duration; +import com.google.protobuf.FieldMask; +import java.io.IOException; + +public class SetProjectTtl { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace this variable before running the sample. + String projectId = "my_project_id"; + + setProjectTtl(projectId); + } + + public static void setProjectTtl(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 (ContactCenterInsightsClient client = ContactCenterInsightsClient.create()) { + // Construct a settings resource. + SettingsName name = SettingsName.of(projectId, "us-central1"); + Settings settings = + Settings.newBuilder() + .setName(name.toString()) + .setConversationTtl(Duration.newBuilder().setSeconds(86400).build()) + .build(); + + // Construct an update mask. + FieldMask updateMask = FieldMask.newBuilder().addPaths("conversation_ttl").build(); + + // Call the Insights client to set a project-level TTL. + Settings response = client.updateSettings(settings, updateMask); + System.out.printf("Set TTL for all incoming conversations to 1 day"); + } + } +} + +// [END contactcenterinsights_set_project_ttl] diff --git a/contact-center-insights/src/test/java/com/example/contactcenterinsights/CreateAnalysisIT.java b/contact-center-insights/src/test/java/com/example/contactcenterinsights/CreateAnalysisIT.java new file mode 100644 index 00000000000..8eb7fd9592a --- /dev/null +++ b/contact-center-insights/src/test/java/com/example/contactcenterinsights/CreateAnalysisIT.java @@ -0,0 +1,110 @@ +/* + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.contactcenterinsights; + +import static com.google.common.truth.Truth.assertThat; +import static junit.framework.TestCase.assertNotNull; + +import com.google.cloud.contactcenterinsights.v1.Analysis; +import com.google.cloud.contactcenterinsights.v1.ContactCenterInsightsClient; +import com.google.cloud.contactcenterinsights.v1.Conversation; +import com.google.cloud.contactcenterinsights.v1.ConversationDataSource; +import com.google.cloud.contactcenterinsights.v1.CreateConversationRequest; +import com.google.cloud.contactcenterinsights.v1.DeleteConversationRequest; +import com.google.cloud.contactcenterinsights.v1.GcsSource; +import com.google.cloud.contactcenterinsights.v1.LocationName; +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 CreateAnalysisIT { + + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String TRANSCRIPT_URI = "gs://cloud-samples-data/ccai/chat_sample.json"; + private static final String AUDIO_URI = "gs://cloud-samples-data/ccai/voice_6912.txt"; + private ByteArrayOutputStream bout; + private PrintStream out; + private String conversationName; + + private static void requireEnvVar(String varName) { + assertNotNull(String.format(varName), String.format(varName)); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() throws IOException { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + + // Create a conversation. + try (ContactCenterInsightsClient client = ContactCenterInsightsClient.create()) { + LocationName parent = LocationName.of(PROJECT_ID, "us-central1"); + + Conversation conversation = + Conversation.newBuilder() + .setDataSource( + ConversationDataSource.newBuilder() + .setGcsSource( + GcsSource.newBuilder() + .setTranscriptUri(TRANSCRIPT_URI) + .setAudioUri(AUDIO_URI) + .build()) + .build()) + .setMedium(Conversation.Medium.CHAT) + .build(); + + CreateConversationRequest request = + CreateConversationRequest.newBuilder() + .setParent(parent.toString()) + .setConversation(conversation) + .build(); + + Conversation response = client.createConversation(request); + conversationName = response.getName(); + } + } + + @After + public void tearDown() throws Exception, IOException { + // Delete the conversation. + try (ContactCenterInsightsClient client = ContactCenterInsightsClient.create()) { + DeleteConversationRequest request = + DeleteConversationRequest.newBuilder().setName(conversationName).setForce(true).build(); + client.deleteConversation(request); + } + System.setOut(null); + } + + @Test + public void testCreateAnalysis() throws Exception, IOException { + Analysis analysis = CreateAnalysis.createAnalysis(conversationName); + assertThat(bout.toString()).contains(analysis.getName()); + } +} diff --git a/contact-center-insights/src/test/java/com/example/contactcenterinsights/CreateConversationIT.java b/contact-center-insights/src/test/java/com/example/contactcenterinsights/CreateConversationIT.java new file mode 100644 index 00000000000..894914fe7c1 --- /dev/null +++ b/contact-center-insights/src/test/java/com/example/contactcenterinsights/CreateConversationIT.java @@ -0,0 +1,76 @@ +/* + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.contactcenterinsights; + +import static com.google.common.truth.Truth.assertThat; +import static junit.framework.TestCase.assertNotNull; + +import com.google.cloud.contactcenterinsights.v1.ContactCenterInsightsClient; +import com.google.cloud.contactcenterinsights.v1.Conversation; +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 CreateConversationIT { + + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String TRANSCRIPT_URI = "gs://cloud-samples-data/ccai/chat_sample.json"; + private static final String AUDIO_URI = "gs://cloud-samples-data/ccai/voice_6912.txt"; + private ByteArrayOutputStream bout; + private PrintStream out; + private String conversationName; + + private static void requireEnvVar(String varName) { + assertNotNull(String.format(varName), String.format(varName)); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() throws IOException { + try (ContactCenterInsightsClient client = ContactCenterInsightsClient.create()) { + client.deleteConversation(conversationName); + } + System.setOut(null); + } + + @Test + public void testCreateConversation() throws IOException { + Conversation conversation = + CreateConversation.createConversation(PROJECT_ID, TRANSCRIPT_URI, AUDIO_URI); + conversationName = conversation.getName(); + assertThat(bout.toString()).contains(conversationName); + } +} diff --git a/contact-center-insights/src/test/java/com/example/contactcenterinsights/CreateConversationWithTtlIT.java b/contact-center-insights/src/test/java/com/example/contactcenterinsights/CreateConversationWithTtlIT.java new file mode 100644 index 00000000000..349795e6056 --- /dev/null +++ b/contact-center-insights/src/test/java/com/example/contactcenterinsights/CreateConversationWithTtlIT.java @@ -0,0 +1,76 @@ +/* + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.contactcenterinsights; + +import static com.google.common.truth.Truth.assertThat; +import static junit.framework.TestCase.assertNotNull; + +import com.google.cloud.contactcenterinsights.v1.ContactCenterInsightsClient; +import com.google.cloud.contactcenterinsights.v1.Conversation; +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 CreateConversationWithTtlIT { + + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String TRANSCRIPT_URI = "gs://cloud-samples-data/ccai/chat_sample.json"; + private static final String AUDIO_URI = "gs://cloud-samples-data/ccai/voice_6912.txt"; + private ByteArrayOutputStream bout; + private PrintStream out; + private String conversationName; + + private static void requireEnvVar(String varName) { + assertNotNull(String.format(varName), String.format(varName)); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() throws IOException { + try (ContactCenterInsightsClient client = ContactCenterInsightsClient.create()) { + client.deleteConversation(conversationName); + } + System.setOut(null); + } + + @Test + public void testCreateConversationWithTtl() throws IOException { + Conversation conversation = + CreateConversationWithTtl.createConversationWithTtl(PROJECT_ID, TRANSCRIPT_URI, AUDIO_URI); + conversationName = conversation.getName(); + assertThat(bout.toString()).contains(conversationName); + } +} diff --git a/contact-center-insights/src/test/java/com/example/contactcenterinsights/CreateIssueModelIT.java b/contact-center-insights/src/test/java/com/example/contactcenterinsights/CreateIssueModelIT.java new file mode 100644 index 00000000000..60e4b9be8c6 --- /dev/null +++ b/contact-center-insights/src/test/java/com/example/contactcenterinsights/CreateIssueModelIT.java @@ -0,0 +1,106 @@ +/* + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.contactcenterinsights; + +import static com.google.common.truth.Truth.assertThat; +import static junit.framework.TestCase.assertNotNull; + +import com.google.cloud.contactcenterinsights.v1.ContactCenterInsightsClient; +import com.google.cloud.contactcenterinsights.v1.IssueModel; +import com.google.cloud.contactcenterinsights.v1.ListConversationsRequest; +import com.google.cloud.contactcenterinsights.v1.ListConversationsResponse; +import com.google.cloud.contactcenterinsights.v1.LocationName; +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 CreateIssueModelIT { + + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final int MIN_CONVERSATION_COUNT = 10000; + private ByteArrayOutputStream bout; + private PrintStream out; + private String issueModelName; + private int conversationCount; + + private static void requireEnvVar(String varName) { + assertNotNull(String.format(varName), String.format(varName)); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() throws IOException { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + + // Check if the project has the minimum number of conversations required to create + // an issue model. See https://cloud.google.com/contact-center/insights/docs/topic-model. + try (ContactCenterInsightsClient client = ContactCenterInsightsClient.create()) { + LocationName parent = LocationName.of(PROJECT_ID, "us-central1"); + ListConversationsRequest.Builder listRequest = + ListConversationsRequest.newBuilder().setParent(parent.toString()).setPageSize(1000); + + conversationCount = 0; + while (conversationCount < MIN_CONVERSATION_COUNT) { + ListConversationsResponse listResponse = + client.listConversationsCallable().call(listRequest.build()); + + if (listResponse.getConversationsCount() == 0) { + break; + } + conversationCount += listResponse.getConversationsCount(); + + if (listResponse.getNextPageToken().isEmpty()) { + break; + } + listRequest.setPageToken(listResponse.getNextPageToken()); + } + } + } + + @After + public void tearDown() throws Exception, IOException { + if (conversationCount >= MIN_CONVERSATION_COUNT) { + try (ContactCenterInsightsClient client = ContactCenterInsightsClient.create()) { + client.deleteIssueModelAsync(issueModelName); + } + } + System.setOut(null); + } + + @Test + public void testCreateIssueModel() throws Exception, IOException { + if (conversationCount >= MIN_CONVERSATION_COUNT) { + IssueModel issueModel = CreateIssueModel.createIssueModel(PROJECT_ID); + issueModelName = issueModel.getName(); + assertThat(bout.toString()).contains(issueModelName); + } + } +} diff --git a/contact-center-insights/src/test/java/com/example/contactcenterinsights/CreatePhraseMatcherAllOfIT.java b/contact-center-insights/src/test/java/com/example/contactcenterinsights/CreatePhraseMatcherAllOfIT.java new file mode 100644 index 00000000000..971150d0b78 --- /dev/null +++ b/contact-center-insights/src/test/java/com/example/contactcenterinsights/CreatePhraseMatcherAllOfIT.java @@ -0,0 +1,73 @@ +/* + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.contactcenterinsights; + +import static com.google.common.truth.Truth.assertThat; +import static junit.framework.TestCase.assertNotNull; + +import com.google.cloud.contactcenterinsights.v1.ContactCenterInsightsClient; +import com.google.cloud.contactcenterinsights.v1.PhraseMatcher; +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 CreatePhraseMatcherAllOfIT { + + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private ByteArrayOutputStream bout; + private PrintStream out; + private String phraseMatcherName; + + private static void requireEnvVar(String varName) { + assertNotNull(String.format(varName), String.format(varName)); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() throws IOException { + try (ContactCenterInsightsClient client = ContactCenterInsightsClient.create()) { + client.deletePhraseMatcher(phraseMatcherName); + } + System.setOut(null); + } + + @Test + public void testCreatePhraseMatcherAllOf() throws IOException { + PhraseMatcher phraseMatcher = CreatePhraseMatcherAllOf.createPhraseMatcherAllOf(PROJECT_ID); + phraseMatcherName = phraseMatcher.getName(); + assertThat(bout.toString()).contains(phraseMatcherName); + } +} diff --git a/contact-center-insights/src/test/java/com/example/contactcenterinsights/CreatePhraseMatcherAnyOfIT.java b/contact-center-insights/src/test/java/com/example/contactcenterinsights/CreatePhraseMatcherAnyOfIT.java new file mode 100644 index 00000000000..ccea1df8c62 --- /dev/null +++ b/contact-center-insights/src/test/java/com/example/contactcenterinsights/CreatePhraseMatcherAnyOfIT.java @@ -0,0 +1,73 @@ +/* + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.contactcenterinsights; + +import static com.google.common.truth.Truth.assertThat; +import static junit.framework.TestCase.assertNotNull; + +import com.google.cloud.contactcenterinsights.v1.ContactCenterInsightsClient; +import com.google.cloud.contactcenterinsights.v1.PhraseMatcher; +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 CreatePhraseMatcherAnyOfIT { + + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private ByteArrayOutputStream bout; + private PrintStream out; + private String phraseMatcherName; + + private static void requireEnvVar(String varName) { + assertNotNull(String.format(varName), String.format(varName)); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() throws IOException { + try (ContactCenterInsightsClient client = ContactCenterInsightsClient.create()) { + client.deletePhraseMatcher(phraseMatcherName); + } + System.setOut(null); + } + + @Test + public void testCreatePhraseMatcherAnyOf() throws IOException { + PhraseMatcher phraseMatcher = CreatePhraseMatcherAnyOf.createPhraseMatcherAnyOf(PROJECT_ID); + phraseMatcherName = phraseMatcher.getName(); + assertThat(bout.toString()).contains(phraseMatcherName); + } +} diff --git a/contact-center-insights/src/test/java/com/example/contactcenterinsights/EnablePubSubNotificationsIT.java b/contact-center-insights/src/test/java/com/example/contactcenterinsights/EnablePubSubNotificationsIT.java new file mode 100644 index 00000000000..5e3188c6e68 --- /dev/null +++ b/contact-center-insights/src/test/java/com/example/contactcenterinsights/EnablePubSubNotificationsIT.java @@ -0,0 +1,109 @@ +/* + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.contactcenterinsights; + +import static com.google.common.truth.Truth.assertThat; +import static junit.framework.TestCase.assertNotNull; + +import com.google.cloud.contactcenterinsights.v1.ContactCenterInsightsClient; +import com.google.cloud.contactcenterinsights.v1.Settings; +import com.google.cloud.contactcenterinsights.v1.SettingsName; +import com.google.cloud.pubsub.v1.TopicAdminClient; +import com.google.protobuf.FieldMask; +import com.google.pubsub.v1.Topic; +import com.google.pubsub.v1.TopicName; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +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) +public class EnablePubSubNotificationsIT { + + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private ByteArrayOutputStream bout; + private PrintStream out; + private String conversationTopic; + private String analysisTopic; + + private static void requireEnvVar(String varName) { + assertNotNull(String.format(varName), String.format(varName)); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() throws IOException { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + + // Create Pub/Sub topics. + try (TopicAdminClient topicAdminClient = TopicAdminClient.create()) { + String conversationTopicId = + String.format("create-conversation-%s", UUID.randomUUID().toString()); + String analysisTopicId = String.format("create-analysis-%s", UUID.randomUUID().toString()); + + conversationTopic = TopicName.of(PROJECT_ID, conversationTopicId).toString(); + analysisTopic = TopicName.of(PROJECT_ID, analysisTopicId).toString(); + String[] topicNames = {conversationTopic, analysisTopic}; + + for (String topicName : topicNames) { + Topic topic = topicAdminClient.createTopic(topicName); + } + } + } + + @After + public void tearDown() throws IOException { + // Disable Pub/Sub notifications. + try (ContactCenterInsightsClient client = ContactCenterInsightsClient.create()) { + SettingsName name = SettingsName.of(PROJECT_ID, "us-central1"); + Settings settings = + Settings.newBuilder().setName(name.toString()).clearPubsubNotificationSettings().build(); + + FieldMask updateMask = + FieldMask.newBuilder().addPaths("pubsub_notification_settings").build(); + + Settings response = client.updateSettings(settings, updateMask); + } + + // Delete Pub/Sub topics. + try (TopicAdminClient topicAdminClient = TopicAdminClient.create()) { + topicAdminClient.deleteTopic(conversationTopic); + topicAdminClient.deleteTopic(analysisTopic); + } + System.setOut(null); + } + + @Test + public void testEnablePubSubNotifications() throws IOException { + EnablePubSubNotifications.enablePubSubNotifications( + PROJECT_ID, conversationTopic, analysisTopic); + assertThat(bout.toString()).contains("Enabled Pub/Sub notifications"); + } +} diff --git a/contact-center-insights/src/test/java/com/example/contactcenterinsights/ExportToBigqueryIT.java b/contact-center-insights/src/test/java/com/example/contactcenterinsights/ExportToBigqueryIT.java new file mode 100644 index 00000000000..a7b68acf8e4 --- /dev/null +++ b/contact-center-insights/src/test/java/com/example/contactcenterinsights/ExportToBigqueryIT.java @@ -0,0 +1,111 @@ +/* + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.contactcenterinsights; + +import static com.google.common.truth.Truth.assertThat; +import static junit.framework.TestCase.assertNotNull; + +import com.google.cloud.bigquery.BigQuery; +import com.google.cloud.bigquery.BigQueryException; +import com.google.cloud.bigquery.BigQueryOptions; +import com.google.cloud.bigquery.Dataset; +import com.google.cloud.bigquery.DatasetId; +import com.google.cloud.bigquery.DatasetInfo; +import com.google.cloud.bigquery.Schema; +import com.google.cloud.bigquery.StandardTableDefinition; +import com.google.cloud.bigquery.Table; +import com.google.cloud.bigquery.TableDefinition; +import com.google.cloud.bigquery.TableId; +import com.google.cloud.bigquery.TableInfo; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +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) +public class ExportToBigqueryIT { + + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String BIGQUERY_PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String GCLOUD_TESTS_PREFIX = "java_samples_tests"; + private ByteArrayOutputStream bout; + private PrintStream out; + private String bigqueryDatasetId; + private String bigqueryTableId; + + private static void requireEnvVar(String varName) { + assertNotNull(String.format(varName), String.format(varName)); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() throws BigQueryException { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + + // Generate BigQuery table and dataset IDs. + bigqueryDatasetId = + String.format("%s_%s", GCLOUD_TESTS_PREFIX, UUID.randomUUID().toString().replace("-", "_")); + bigqueryTableId = + String.format("%s_%s", GCLOUD_TESTS_PREFIX, UUID.randomUUID().toString().replace("-", "_")); + + // Create a BigQuery dataset. + BigQuery bigquery = BigQueryOptions.getDefaultInstance().getService(); + DatasetInfo datasetInfo = + DatasetInfo.newBuilder(DatasetId.of(BIGQUERY_PROJECT_ID, bigqueryDatasetId)).build(); + Dataset dataset = bigquery.create(datasetInfo); + + // Create a BigQuery table under the created dataset. + Schema schema = Schema.of(new ArrayList<>()); + TableDefinition tableDefinition = StandardTableDefinition.of(schema); + TableInfo tableInfo = + TableInfo.newBuilder(TableId.of(bigqueryDatasetId, bigqueryTableId), tableDefinition) + .build(); + Table table = bigquery.create(tableInfo); + } + + @After + public void tearDown() throws BigQueryException { + // Delete the BigQuery dataset and table. + BigQuery bigquery = BigQueryOptions.getDefaultInstance().getService(); + boolean success = + bigquery.delete( + DatasetId.of(PROJECT_ID, bigqueryDatasetId), + BigQuery.DatasetDeleteOption.deleteContents()); + System.setOut(null); + } + + @Test + public void testExportToBigquery() throws Exception, IOException { + ExportToBigquery.exportToBigquery( + PROJECT_ID, BIGQUERY_PROJECT_ID, bigqueryDatasetId, bigqueryTableId); + assertThat(bout.toString()).contains("Exported data to BigQuery"); + } +} diff --git a/contact-center-insights/src/test/java/com/example/contactcenterinsights/GetOperationIT.java b/contact-center-insights/src/test/java/com/example/contactcenterinsights/GetOperationIT.java new file mode 100644 index 00000000000..17e9abdb110 --- /dev/null +++ b/contact-center-insights/src/test/java/com/example/contactcenterinsights/GetOperationIT.java @@ -0,0 +1,79 @@ +/* + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.contactcenterinsights; + +import static com.google.common.truth.Truth.assertThat; +import static junit.framework.TestCase.assertNotNull; + +import com.google.api.gax.rpc.ApiException; +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class GetOperationIT { + + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private ByteArrayOutputStream bout; + private PrintStream out; + private String conversationName; + + private static void requireEnvVar(String varName) { + assertNotNull(String.format(varName), String.format(varName)); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() throws IOException { + System.setOut(null); + } + + @Test + public void testGetOperation() throws IOException { + // TODO(developer): Replace this variable with your operation name. + String operationName = + String.format("projects/%s/locations/us-central1/operations/12345", PROJECT_ID); + + try { + Operation operation = GetOperation.getOperation(operationName); + assertThat(bout.toString()).contains(operation.getName()); + } catch (ApiException exception) { + if (!exception.getMessage().contains("not found")) { + throw exception; + } + } + } +} diff --git a/contact-center-insights/src/test/java/com/example/contactcenterinsights/SetProjectTtlIT.java b/contact-center-insights/src/test/java/com/example/contactcenterinsights/SetProjectTtlIT.java new file mode 100644 index 00000000000..adb973d3414 --- /dev/null +++ b/contact-center-insights/src/test/java/com/example/contactcenterinsights/SetProjectTtlIT.java @@ -0,0 +1,84 @@ +/* + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.contactcenterinsights; + +import static com.google.common.truth.Truth.assertThat; +import static junit.framework.TestCase.assertNotNull; + +import com.google.cloud.contactcenterinsights.v1.ContactCenterInsightsClient; +import com.google.cloud.contactcenterinsights.v1.Settings; +import com.google.cloud.contactcenterinsights.v1.SettingsName; +import com.google.protobuf.Duration; +import com.google.protobuf.FieldMask; +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 SetProjectTtlIT { + + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private ByteArrayOutputStream bout; + private PrintStream out; + + private static void requireEnvVar(String varName) { + assertNotNull(String.format(varName), String.format(varName)); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() throws IOException { + try (ContactCenterInsightsClient client = ContactCenterInsightsClient.create()) { + // Clear project-level TTL. + SettingsName name = SettingsName.of(PROJECT_ID, "us-central1"); + Settings settings = + Settings.newBuilder() + .setName(name.toString()) + .setConversationTtl(Duration.newBuilder().build()) + .build(); + + FieldMask updateMask = FieldMask.newBuilder().addPaths("conversation_ttl").build(); + + Settings response = client.updateSettings(settings, updateMask); + } + System.setOut(null); + } + + @Test + public void testSetProjecTtl() throws IOException { + SetProjectTtl.setProjectTtl(PROJECT_ID); + assertThat(bout.toString()).contains("Set TTL for all incoming conversations to 1 day"); + } +} diff --git a/dataproc/pom.xml b/dataproc/pom.xml new file mode 100644 index 00000000000..60f66e3301e --- /dev/null +++ b/dataproc/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + com.example.dataproc + dataproc-snippets + jar + Google Dataproc Snippets + https://github.com/GoogleCloudPlatform/java-docs-samples/tree/main/dataproc + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + 1.8 + 1.8 + UTF-8 + + + + + + + + com.google.cloud + libraries-bom + 26.1.3 + pom + import + + + + + + + com.google.cloud + google-cloud-dataproc + + + + com.google.cloud + google-cloud-storage + + + junit + junit + 4.13.2 + test + + + com.google.truth + truth + 1.1.3 + test + + + + + diff --git a/dataproc/src/main/java/CreateCluster.java b/dataproc/src/main/java/CreateCluster.java new file mode 100644 index 00000000000..0623c8cc465 --- /dev/null +++ b/dataproc/src/main/java/CreateCluster.java @@ -0,0 +1,84 @@ +/* + * 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. + */ + +// [START dataproc_create_cluster] +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.dataproc.v1.Cluster; +import com.google.cloud.dataproc.v1.ClusterConfig; +import com.google.cloud.dataproc.v1.ClusterControllerClient; +import com.google.cloud.dataproc.v1.ClusterControllerSettings; +import com.google.cloud.dataproc.v1.ClusterOperationMetadata; +import com.google.cloud.dataproc.v1.InstanceGroupConfig; +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +public class CreateCluster { + + public static void createCluster() throws IOException, InterruptedException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String region = "your-project-region"; + String clusterName = "your-cluster-name"; + createCluster(projectId, region, clusterName); + } + + public static void createCluster(String projectId, String region, String clusterName) + throws IOException, InterruptedException { + String myEndpoint = String.format("%s-dataproc.googleapis.com:443", region); + + // Configure the settings for the cluster controller client. + ClusterControllerSettings clusterControllerSettings = + ClusterControllerSettings.newBuilder().setEndpoint(myEndpoint).build(); + + // Create a cluster controller client with the configured settings. The client only needs to be + // created once and can be reused for multiple requests. Using a try-with-resources + // closes the client, but this can also be done manually with the .close() method. + try (ClusterControllerClient clusterControllerClient = + ClusterControllerClient.create(clusterControllerSettings)) { + // Configure the settings for our cluster. + InstanceGroupConfig masterConfig = + InstanceGroupConfig.newBuilder() + .setMachineTypeUri("n1-standard-2") + .setNumInstances(1) + .build(); + InstanceGroupConfig workerConfig = + InstanceGroupConfig.newBuilder() + .setMachineTypeUri("n1-standard-2") + .setNumInstances(2) + .build(); + ClusterConfig clusterConfig = + ClusterConfig.newBuilder() + .setMasterConfig(masterConfig) + .setWorkerConfig(workerConfig) + .build(); + // Create the cluster object with the desired cluster config. + Cluster cluster = + Cluster.newBuilder().setClusterName(clusterName).setConfig(clusterConfig).build(); + + // Create the Cloud Dataproc cluster. + OperationFuture createClusterAsyncRequest = + clusterControllerClient.createClusterAsync(projectId, region, cluster); + Cluster response = createClusterAsyncRequest.get(); + + // Print out a success message. + System.out.printf("Cluster created successfully: %s", response.getClusterName()); + + } catch (ExecutionException e) { + System.err.println(String.format("Error executing createCluster: %s ", e.getMessage())); + } + } +} +// [END dataproc_create_cluster] diff --git a/dataproc/src/main/java/CreateClusterWithAutoscaling.java b/dataproc/src/main/java/CreateClusterWithAutoscaling.java new file mode 100644 index 00000000000..981721a497a --- /dev/null +++ b/dataproc/src/main/java/CreateClusterWithAutoscaling.java @@ -0,0 +1,174 @@ +/* +* 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. +* +* This sample creates a Dataproc cluster with an autoscaling policy enabled. +* The policy we will be creating mirrors the following YAML representation: +* + workerConfig: + minInstances: 2 + maxInstances: 100 + weight: 1 + secondaryWorkerConfig: + minInstances: 0 + maxInstances: 100 + weight: 1 + basicAlgorithm: + cooldownPeriod: 4m + yarnConfig: + scaleUpFactor: 0.05 + scaleDownFactor: 1.0 + scaleUpMinWorkerFraction: 0.0 + scaleDownMinWorkerFraction: 0.0 + gracefulDecommissionTimeout: 1h +*/ + +// [START dataproc_create_autoscaling_cluster] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.dataproc.v1.AutoscalingConfig; +import com.google.cloud.dataproc.v1.AutoscalingPolicy; +import com.google.cloud.dataproc.v1.AutoscalingPolicyServiceClient; +import com.google.cloud.dataproc.v1.AutoscalingPolicyServiceSettings; +import com.google.cloud.dataproc.v1.BasicAutoscalingAlgorithm; +import com.google.cloud.dataproc.v1.BasicYarnAutoscalingConfig; +import com.google.cloud.dataproc.v1.Cluster; +import com.google.cloud.dataproc.v1.ClusterConfig; +import com.google.cloud.dataproc.v1.ClusterControllerClient; +import com.google.cloud.dataproc.v1.ClusterControllerSettings; +import com.google.cloud.dataproc.v1.ClusterOperationMetadata; +import com.google.cloud.dataproc.v1.InstanceGroupAutoscalingPolicyConfig; +import com.google.cloud.dataproc.v1.InstanceGroupConfig; +import com.google.cloud.dataproc.v1.RegionName; +import com.google.protobuf.Duration; +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +public class CreateClusterWithAutoscaling { + + public static void createClusterwithAutoscaling() throws IOException, InterruptedException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String region = "your-project-region"; + String clusterName = "your-cluster-name"; + String autoscalingPolicyName = "your-autoscaling-policy"; + createClusterwithAutoscaling(projectId, region, clusterName, autoscalingPolicyName); + } + + public static void createClusterwithAutoscaling( + String projectId, String region, String clusterName, String autoscalingPolicyName) + throws IOException, InterruptedException { + String myEndpoint = String.format("%s-dataproc.googleapis.com:443", region); + + // Configure the settings for the cluster controller client. + ClusterControllerSettings clusterControllerSettings = + ClusterControllerSettings.newBuilder().setEndpoint(myEndpoint).build(); + + // Configure the settings for the autoscaling policy service client. + AutoscalingPolicyServiceSettings autoscalingPolicyServiceSettings = + AutoscalingPolicyServiceSettings.newBuilder().setEndpoint(myEndpoint).build(); + + // Create a cluster controller client and an autoscaling controller client with the configured + // settings. The clients only need to be created once and can be reused for multiple requests. + // Using a + // try-with-resources closes the client, but this can also be done manually with the .close() + // method. + try (ClusterControllerClient clusterControllerClient = + ClusterControllerClient.create(clusterControllerSettings); + AutoscalingPolicyServiceClient autoscalingPolicyServiceClient = + AutoscalingPolicyServiceClient.create(autoscalingPolicyServiceSettings)) { + + // Create the Autoscaling policy. + InstanceGroupAutoscalingPolicyConfig workerInstanceGroupAutoscalingPolicyConfig = + InstanceGroupAutoscalingPolicyConfig.newBuilder() + .setMinInstances(2) + .setMaxInstances(100) + .setWeight(1) + .build(); + InstanceGroupAutoscalingPolicyConfig secondaryWorkerInstanceGroupAutoscalingPolicyConfig = + InstanceGroupAutoscalingPolicyConfig.newBuilder() + .setMinInstances(0) + .setMaxInstances(100) + .setWeight(1) + .build(); + BasicYarnAutoscalingConfig basicYarnApplicationConfig = + BasicYarnAutoscalingConfig.newBuilder() + .setScaleUpFactor(0.05) + .setScaleDownFactor(1.0) + .setScaleUpMinWorkerFraction(0.0) + .setScaleUpMinWorkerFraction(0.0) + .setGracefulDecommissionTimeout(Duration.newBuilder().setSeconds(3600).build()) + .build(); + BasicAutoscalingAlgorithm basicAutoscalingAlgorithm = + BasicAutoscalingAlgorithm.newBuilder() + .setCooldownPeriod(Duration.newBuilder().setSeconds(240).build()) + .setYarnConfig(basicYarnApplicationConfig) + .build(); + AutoscalingPolicy autoscalingPolicy = + AutoscalingPolicy.newBuilder() + .setId(autoscalingPolicyName) + .setWorkerConfig(workerInstanceGroupAutoscalingPolicyConfig) + .setSecondaryWorkerConfig(secondaryWorkerInstanceGroupAutoscalingPolicyConfig) + .setBasicAlgorithm(basicAutoscalingAlgorithm) + .build(); + RegionName parent = RegionName.of(projectId, region); + + // Policy is uploaded here. + autoscalingPolicyServiceClient.createAutoscalingPolicy(parent, autoscalingPolicy); + + // Now the policy can be referenced when creating a cluster. + String autoscalingPolicyUri = + String.format( + "projects/%s/locations/%s/autoscalingPolicies/%s", + projectId, region, autoscalingPolicyName); + AutoscalingConfig autoscalingConfig = + AutoscalingConfig.newBuilder().setPolicyUri(autoscalingPolicyUri).build(); + + // Configure the settings for our cluster. + InstanceGroupConfig masterConfig = + InstanceGroupConfig.newBuilder() + .setMachineTypeUri("n1-standard-2") + .setNumInstances(1) + .build(); + InstanceGroupConfig workerConfig = + InstanceGroupConfig.newBuilder() + .setMachineTypeUri("n1-standard-2") + .setNumInstances(2) + .build(); + ClusterConfig clusterConfig = + ClusterConfig.newBuilder() + .setMasterConfig(masterConfig) + .setWorkerConfig(workerConfig) + .setAutoscalingConfig(autoscalingConfig) + .build(); + + // Create the cluster object with the desired cluster config. + Cluster cluster = + Cluster.newBuilder().setClusterName(clusterName).setConfig(clusterConfig).build(); + + // Create the Dataproc cluster. + OperationFuture createClusterAsyncRequest = + clusterControllerClient.createClusterAsync(projectId, region, cluster); + Cluster response = createClusterAsyncRequest.get(); + + // Print out a success message. + System.out.printf("Cluster created successfully: %s", response.getClusterName()); + + } catch (ExecutionException e) { + // If cluster creation does not complete successfully, print the error message. + System.err.println(String.format("createClusterWithAutoscaling: %s ", e.getMessage())); + } + } +} +// [END dataproc_create_autoscaling_cluster] diff --git a/dataproc/src/main/java/InstantiateInlineWorkflowTemplate.java b/dataproc/src/main/java/InstantiateInlineWorkflowTemplate.java new file mode 100644 index 00000000000..4f3e73799c7 --- /dev/null +++ b/dataproc/src/main/java/InstantiateInlineWorkflowTemplate.java @@ -0,0 +1,121 @@ +/* + * 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. + */ + +// [START dataproc_instantiate_inline_workflow_template] +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.dataproc.v1.ClusterConfig; +import com.google.cloud.dataproc.v1.GceClusterConfig; +import com.google.cloud.dataproc.v1.HadoopJob; +import com.google.cloud.dataproc.v1.ManagedCluster; +import com.google.cloud.dataproc.v1.OrderedJob; +import com.google.cloud.dataproc.v1.RegionName; +import com.google.cloud.dataproc.v1.WorkflowMetadata; +import com.google.cloud.dataproc.v1.WorkflowTemplate; +import com.google.cloud.dataproc.v1.WorkflowTemplatePlacement; +import com.google.cloud.dataproc.v1.WorkflowTemplateServiceClient; +import com.google.cloud.dataproc.v1.WorkflowTemplateServiceSettings; +import com.google.protobuf.Empty; +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +public class InstantiateInlineWorkflowTemplate { + + public static void instantiateInlineWorkflowTemplate() throws IOException, InterruptedException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String region = "your-project-region"; + instantiateInlineWorkflowTemplate(projectId, region); + } + + public static void instantiateInlineWorkflowTemplate(String projectId, String region) + throws IOException, InterruptedException { + String myEndpoint = String.format("%s-dataproc.googleapis.com:443", region); + + // Configure the settings for the workflow template service client. + WorkflowTemplateServiceSettings workflowTemplateServiceSettings = + WorkflowTemplateServiceSettings.newBuilder().setEndpoint(myEndpoint).build(); + + // Create a workflow template service client with the configured settings. The client only + // needs to be created once and can be reused for multiple requests. Using a try-with-resources + // closes the client, but this can also be done manually with the .close() method. + try (WorkflowTemplateServiceClient workflowTemplateServiceClient = + WorkflowTemplateServiceClient.create(workflowTemplateServiceSettings)) { + + // Configure the jobs within the workflow. + HadoopJob teragenHadoopJob = + HadoopJob.newBuilder() + .setMainJarFileUri("file:///usr/lib/hadoop-mapreduce/hadoop-mapreduce-examples.jar") + .addArgs("teragen") + .addArgs("1000") + .addArgs("hdfs:///gen/") + .build(); + OrderedJob teragen = + OrderedJob.newBuilder().setHadoopJob(teragenHadoopJob).setStepId("teragen").build(); + + HadoopJob terasortHadoopJob = + HadoopJob.newBuilder() + .setMainJarFileUri("file:///usr/lib/hadoop-mapreduce/hadoop-mapreduce-examples.jar") + .addArgs("terasort") + .addArgs("hdfs:///gen/") + .addArgs("hdfs:///sort/") + .build(); + OrderedJob terasort = + OrderedJob.newBuilder() + .setHadoopJob(terasortHadoopJob) + .addPrerequisiteStepIds("teragen") + .setStepId("terasort") + .build(); + + // Configure the cluster placement for the workflow. + // Leave "ZoneUri" empty for "Auto Zone Placement". + // GceClusterConfig gceClusterConfig = + // GceClusterConfig.newBuilder().setZoneUri("").build(); + GceClusterConfig gceClusterConfig = + GceClusterConfig.newBuilder().setZoneUri("us-central1-a").build(); + ClusterConfig clusterConfig = + ClusterConfig.newBuilder().setGceClusterConfig(gceClusterConfig).build(); + ManagedCluster managedCluster = + ManagedCluster.newBuilder() + .setClusterName("my-managed-cluster") + .setConfig(clusterConfig) + .build(); + WorkflowTemplatePlacement workflowTemplatePlacement = + WorkflowTemplatePlacement.newBuilder().setManagedCluster(managedCluster).build(); + + // Create the inline workflow template. + WorkflowTemplate workflowTemplate = + WorkflowTemplate.newBuilder() + .addJobs(teragen) + .addJobs(terasort) + .setPlacement(workflowTemplatePlacement) + .build(); + + // Submit the instantiated inline workflow template request. + String parent = RegionName.format(projectId, region); + OperationFuture instantiateInlineWorkflowTemplateAsync = + workflowTemplateServiceClient.instantiateInlineWorkflowTemplateAsync( + parent, workflowTemplate); + instantiateInlineWorkflowTemplateAsync.get(); + + // Print out a success message. + System.out.printf("Workflow ran successfully."); + + } catch (ExecutionException e) { + System.err.println(String.format("Error running workflow: %s ", e.getMessage())); + } + } +} +// [END dataproc_instantiate_inline_workflow_template] diff --git a/dataproc/src/main/java/Quickstart.java b/dataproc/src/main/java/Quickstart.java new file mode 100644 index 00000000000..f7911313cf1 --- /dev/null +++ b/dataproc/src/main/java/Quickstart.java @@ -0,0 +1,151 @@ +/* + * 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. + */ + +// [START dataproc_quickstart] +/* This quickstart sample walks a user through creating a Cloud Dataproc + * cluster, submitting a PySpark job from Google Cloud Storage to the + * cluster, reading the output of the job and deleting the cluster, all + * using the Java client library. + * + * Usage: + * mvn clean package -DskipTests + * + * mvn exec:java -Dexec.args=" " + * + * You can also set these arguments in the main function instead of providing them via the CLI. + */ + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.dataproc.v1.Cluster; +import com.google.cloud.dataproc.v1.ClusterConfig; +import com.google.cloud.dataproc.v1.ClusterControllerClient; +import com.google.cloud.dataproc.v1.ClusterControllerSettings; +import com.google.cloud.dataproc.v1.ClusterOperationMetadata; +import com.google.cloud.dataproc.v1.InstanceGroupConfig; +import com.google.cloud.dataproc.v1.Job; +import com.google.cloud.dataproc.v1.JobControllerClient; +import com.google.cloud.dataproc.v1.JobControllerSettings; +import com.google.cloud.dataproc.v1.JobMetadata; +import com.google.cloud.dataproc.v1.JobPlacement; +import com.google.cloud.dataproc.v1.PySparkJob; +import com.google.cloud.storage.Blob; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import com.google.protobuf.Empty; +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Quickstart { + + public static void quickstart( + String projectId, String region, String clusterName, String jobFilePath) + throws IOException, InterruptedException { + String myEndpoint = String.format("%s-dataproc.googleapis.com:443", region); + + // Configure the settings for the cluster controller client. + ClusterControllerSettings clusterControllerSettings = + ClusterControllerSettings.newBuilder().setEndpoint(myEndpoint).build(); + + // Configure the settings for the job controller client. + JobControllerSettings jobControllerSettings = + JobControllerSettings.newBuilder().setEndpoint(myEndpoint).build(); + + // Create both a cluster controller client and job controller client with the + // configured settings. The client only needs to be created once and can be reused for + // multiple requests. Using a try-with-resources closes the client, but this can also be done + // manually with the .close() method. + try (ClusterControllerClient clusterControllerClient = + ClusterControllerClient.create(clusterControllerSettings); + JobControllerClient jobControllerClient = + JobControllerClient.create(jobControllerSettings)) { + // Configure the settings for our cluster. + InstanceGroupConfig masterConfig = + InstanceGroupConfig.newBuilder() + .setMachineTypeUri("n1-standard-2") + .setNumInstances(1) + .build(); + InstanceGroupConfig workerConfig = + InstanceGroupConfig.newBuilder() + .setMachineTypeUri("n1-standard-2") + .setNumInstances(2) + .build(); + ClusterConfig clusterConfig = + ClusterConfig.newBuilder() + .setMasterConfig(masterConfig) + .setWorkerConfig(workerConfig) + .build(); + // Create the cluster object with the desired cluster config. + Cluster cluster = + Cluster.newBuilder().setClusterName(clusterName).setConfig(clusterConfig).build(); + + // Create the Cloud Dataproc cluster. + OperationFuture createClusterAsyncRequest = + clusterControllerClient.createClusterAsync(projectId, region, cluster); + Cluster clusterResponse = createClusterAsyncRequest.get(); + System.out.println( + String.format("Cluster created successfully: %s", clusterResponse.getClusterName())); + + // Configure the settings for our job. + JobPlacement jobPlacement = JobPlacement.newBuilder().setClusterName(clusterName).build(); + PySparkJob pySparkJob = PySparkJob.newBuilder().setMainPythonFileUri(jobFilePath).build(); + Job job = Job.newBuilder().setPlacement(jobPlacement).setPysparkJob(pySparkJob).build(); + + // Submit an asynchronous request to execute the job. + OperationFuture submitJobAsOperationAsyncRequest = + jobControllerClient.submitJobAsOperationAsync(projectId, region, job); + Job jobResponse = submitJobAsOperationAsyncRequest.get(); + + // Print output from Google Cloud Storage. + Matcher matches = + Pattern.compile("gs://(.*?)/(.*)").matcher(jobResponse.getDriverOutputResourceUri()); + matches.matches(); + + Storage storage = StorageOptions.getDefaultInstance().getService(); + Blob blob = storage.get(matches.group(1), String.format("%s.000000000", matches.group(2))); + + System.out.println( + String.format("Job finished successfully: %s", new String(blob.getContent()))); + + // Delete the cluster. + OperationFuture deleteClusterAsyncRequest = + clusterControllerClient.deleteClusterAsync(projectId, region, clusterName); + deleteClusterAsyncRequest.get(); + System.out.println(String.format("Cluster \"%s\" successfully deleted.", clusterName)); + + } catch (ExecutionException e) { + System.err.println(String.format("quickstart: %s ", e.getMessage())); + } + } + + public static void main(String... args) throws IOException, InterruptedException { + if (args.length != 4) { + System.err.println( + "Insufficient number of parameters provided. Please make sure a " + + "PROJECT_ID, REGION, CLUSTER_NAME and JOB_FILE_PATH are provided, in this order."); + return; + } + + String projectId = args[0]; // project-id of project to create the cluster in + String region = args[1]; // region to create the cluster + String clusterName = args[2]; // name of the cluster + String jobFilePath = args[3]; // location in GCS of the PySpark job + + quickstart(projectId, region, clusterName, jobFilePath); + } +} +// [END dataproc_quickstart] diff --git a/dataproc/src/main/java/SubmitHadoopFsJob.java b/dataproc/src/main/java/SubmitHadoopFsJob.java new file mode 100644 index 00000000000..0c26416c41b --- /dev/null +++ b/dataproc/src/main/java/SubmitHadoopFsJob.java @@ -0,0 +1,101 @@ +/* + * 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. + */ + +// [START dataproc_submit_hadoop_fs_job] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.dataproc.v1.HadoopJob; +import com.google.cloud.dataproc.v1.Job; +import com.google.cloud.dataproc.v1.JobControllerClient; +import com.google.cloud.dataproc.v1.JobControllerSettings; +import com.google.cloud.dataproc.v1.JobMetadata; +import com.google.cloud.dataproc.v1.JobPlacement; +import com.google.cloud.storage.Blob; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.ExecutionException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SubmitHadoopFsJob { + + public static ArrayList stringToList(String s) { + return new ArrayList<>(Arrays.asList(s.split(" "))); + } + + public static void submitHadoopFsJob() throws IOException, InterruptedException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String region = "your-project-region"; + String clusterName = "your-cluster-name"; + String hadoopFsQuery = "your-hadoop-fs-query"; + submitHadoopFsJob(projectId, region, clusterName, hadoopFsQuery); + } + + public static void submitHadoopFsJob( + String projectId, String region, String clusterName, String hadoopFsQuery) + throws IOException, InterruptedException { + String myEndpoint = String.format("%s-dataproc.googleapis.com:443", region); + + // Configure the settings for the job controller client. + JobControllerSettings jobControllerSettings = + JobControllerSettings.newBuilder().setEndpoint(myEndpoint).build(); + + // Create a job controller client with the configured settings. Using a try-with-resources + // closes the client, + // but this can also be done manually with the .close() method. + try (JobControllerClient jobControllerClient = + JobControllerClient.create(jobControllerSettings)) { + + // Configure cluster placement for the job. + JobPlacement jobPlacement = JobPlacement.newBuilder().setClusterName(clusterName).build(); + + // Configure Hadoop job settings. The HadoopFS query is set here. + HadoopJob hadoopJob = + HadoopJob.newBuilder() + .setMainClass("org.apache.hadoop.fs.FsShell") + .addAllArgs(stringToList(hadoopFsQuery)) + .build(); + + Job job = Job.newBuilder().setPlacement(jobPlacement).setHadoopJob(hadoopJob).build(); + + // Submit an asynchronous request to execute the job. + OperationFuture submitJobAsOperationAsyncRequest = + jobControllerClient.submitJobAsOperationAsync(projectId, region, job); + + Job response = submitJobAsOperationAsyncRequest.get(); + + // Print output from Google Cloud Storage. + Matcher matches = + Pattern.compile("gs://(.*?)/(.*)").matcher(response.getDriverOutputResourceUri()); + matches.matches(); + + Storage storage = StorageOptions.getDefaultInstance().getService(); + Blob blob = storage.get(matches.group(1), String.format("%s.000000000", matches.group(2))); + + System.out.println( + String.format("Job finished successfully: %s", new String(blob.getContent()))); + + } catch (ExecutionException e) { + // If the job does not complete successfully, print the error message. + System.err.println(String.format("submitHadoopFSJob: %s ", e.getMessage())); + } + } +} +// [END dataproc_submit_hadoop_fs_job] diff --git a/dataproc/src/main/java/SubmitJob.java b/dataproc/src/main/java/SubmitJob.java new file mode 100644 index 00000000000..93193a7aca5 --- /dev/null +++ b/dataproc/src/main/java/SubmitJob.java @@ -0,0 +1,94 @@ +/* + * 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. + */ + +// [START dataproc_submit_job] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.dataproc.v1.Job; +import com.google.cloud.dataproc.v1.JobControllerClient; +import com.google.cloud.dataproc.v1.JobControllerSettings; +import com.google.cloud.dataproc.v1.JobMetadata; +import com.google.cloud.dataproc.v1.JobPlacement; +import com.google.cloud.dataproc.v1.SparkJob; +import com.google.cloud.storage.Blob; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SubmitJob { + + public static void submitJob() throws IOException, InterruptedException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String region = "your-project-region"; + String clusterName = "your-cluster-name"; + submitJob(projectId, region, clusterName); + } + + public static void submitJob(String projectId, String region, String clusterName) + throws IOException, InterruptedException { + String myEndpoint = String.format("%s-dataproc.googleapis.com:443", region); + + // Configure the settings for the job controller client. + JobControllerSettings jobControllerSettings = + JobControllerSettings.newBuilder().setEndpoint(myEndpoint).build(); + + // Create a job controller client with the configured settings. Using a try-with-resources + // closes the client, + // but this can also be done manually with the .close() method. + try (JobControllerClient jobControllerClient = + JobControllerClient.create(jobControllerSettings)) { + + // Configure cluster placement for the job. + JobPlacement jobPlacement = JobPlacement.newBuilder().setClusterName(clusterName).build(); + + // Configure Spark job settings. + SparkJob sparkJob = + SparkJob.newBuilder() + .setMainClass("org.apache.spark.examples.SparkPi") + .addJarFileUris("file:///usr/lib/spark/examples/jars/spark-examples.jar") + .addArgs("1000") + .build(); + + Job job = Job.newBuilder().setPlacement(jobPlacement).setSparkJob(sparkJob).build(); + + // Submit an asynchronous request to execute the job. + OperationFuture submitJobAsOperationAsyncRequest = + jobControllerClient.submitJobAsOperationAsync(projectId, region, job); + + Job response = submitJobAsOperationAsyncRequest.get(); + + // Print output from Google Cloud Storage. + Matcher matches = + Pattern.compile("gs://(.*?)/(.*)").matcher(response.getDriverOutputResourceUri()); + matches.matches(); + + Storage storage = StorageOptions.getDefaultInstance().getService(); + Blob blob = storage.get(matches.group(1), String.format("%s.000000000", matches.group(2))); + + System.out.println( + String.format("Job finished successfully: %s", new String(blob.getContent()))); + + } catch (ExecutionException e) { + // If the job does not complete successfully, print the error message. + System.err.println(String.format("submitJob: %s ", e.getMessage())); + } + } +} +// [END dataproc_submit_job] diff --git a/dataproc/src/test/java/CreateClusterTest.java b/dataproc/src/test/java/CreateClusterTest.java new file mode 100644 index 00000000000..9e8cb1affc1 --- /dev/null +++ b/dataproc/src/test/java/CreateClusterTest.java @@ -0,0 +1,87 @@ +/* + * 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. + */ + +import static junit.framework.TestCase.assertNotNull; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.dataproc.v1.ClusterControllerClient; +import com.google.cloud.dataproc.v1.ClusterControllerSettings; +import com.google.cloud.dataproc.v1.ClusterOperationMetadata; +import com.google.protobuf.Empty; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import org.hamcrest.CoreMatchers; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class CreateClusterTest { + + private static final String CLUSTER_NAME = + String.format("java-cc-test-%s", UUID.randomUUID().toString()); + private static final String REGION = "us-central1"; + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + // private static final String PROJECT_ID = "gcloud-devel"; + private ByteArrayOutputStream bout; + + private static void requireEnv(String varName) { + assertNotNull( + String.format("Environment variable '%s' is required to perform these tests.", varName), + System.getenv(varName)); + } + /* + @BeforeClass + public static void checkRequirements() { + requireEnv("GOOGLE_APPLICATION_CREDENTIALS"); + requireEnv("GOOGLE_CLOUD_PROJECT"); + }*/ + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(bout)); + } + + @Test + public void createClusterTest() throws IOException, InterruptedException { + CreateCluster.createCluster(PROJECT_ID, REGION, CLUSTER_NAME); + String output = bout.toString(); + + assertThat(output, CoreMatchers.containsString(CLUSTER_NAME)); + } + + @After + public void tearDown() throws IOException, InterruptedException, ExecutionException { + String myEndpoint = String.format("%s-dataproc.googleapis.com:443", REGION); + + ClusterControllerSettings clusterControllerSettings = + ClusterControllerSettings.newBuilder().setEndpoint(myEndpoint).build(); + + try (ClusterControllerClient clusterControllerClient = + ClusterControllerClient.create(clusterControllerSettings)) { + OperationFuture deleteClusterAsyncRequest = + clusterControllerClient.deleteClusterAsync(PROJECT_ID, REGION, CLUSTER_NAME); + deleteClusterAsyncRequest.get(); + } + } +} diff --git a/dataproc/src/test/java/CreateClusterWithAutoscalingTest.java b/dataproc/src/test/java/CreateClusterWithAutoscalingTest.java new file mode 100644 index 00000000000..1b8f15058b3 --- /dev/null +++ b/dataproc/src/test/java/CreateClusterWithAutoscalingTest.java @@ -0,0 +1,105 @@ +/* + * 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. + */ + +import static junit.framework.TestCase.assertNotNull; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.dataproc.v1.AutoscalingPolicyName; +import com.google.cloud.dataproc.v1.AutoscalingPolicyServiceClient; +import com.google.cloud.dataproc.v1.AutoscalingPolicyServiceSettings; +import com.google.cloud.dataproc.v1.ClusterControllerClient; +import com.google.cloud.dataproc.v1.ClusterControllerSettings; +import com.google.cloud.dataproc.v1.ClusterOperationMetadata; +import com.google.protobuf.Empty; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import org.hamcrest.CoreMatchers; +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 CreateClusterWithAutoscalingTest { + + private static final String CLUSTER_NAME = + String.format("java-as-test-%s", UUID.randomUUID().toString()); + private static final String REGION = "us-central1"; + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String AUTOSCALING_POLICY_NAME = + String.format("java-as-test-%s", UUID.randomUUID().toString()); + + private ByteArrayOutputStream bout; + + private static void requireEnv(String varName) { + assertNotNull( + String.format("Environment variable '%s' is required to perform these tests.", varName), + System.getenv(varName)); + } + + @BeforeClass + public static void checkRequirements() { + requireEnv("GOOGLE_APPLICATION_CREDENTIALS"); + requireEnv("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(bout)); + } + + @After + public void tearDown() throws IOException, InterruptedException, ExecutionException { + String myEndpoint = String.format("%s-dataproc.googleapis.com:443", REGION); + + ClusterControllerSettings clusterControllerSettings = + ClusterControllerSettings.newBuilder().setEndpoint(myEndpoint).build(); + + AutoscalingPolicyServiceSettings autoscalingPolicyServiceSettings = + AutoscalingPolicyServiceSettings.newBuilder().setEndpoint(myEndpoint).build(); + + try (ClusterControllerClient clusterControllerClient = + ClusterControllerClient.create(clusterControllerSettings); + AutoscalingPolicyServiceClient autoscalingPolicyServiceClient = + AutoscalingPolicyServiceClient.create(autoscalingPolicyServiceSettings)) { + + OperationFuture deleteClusterAsyncRequest = + clusterControllerClient.deleteClusterAsync(PROJECT_ID, REGION, CLUSTER_NAME); + deleteClusterAsyncRequest.get(); + + AutoscalingPolicyName name = + AutoscalingPolicyName.ofProjectLocationAutoscalingPolicyName( + PROJECT_ID, REGION, AUTOSCALING_POLICY_NAME); + autoscalingPolicyServiceClient.deleteAutoscalingPolicy(name); + } + } + + @Test + public void createClusterWithAutoscalingTest() throws IOException, InterruptedException { + CreateClusterWithAutoscaling.createClusterwithAutoscaling( + PROJECT_ID, REGION, CLUSTER_NAME, AUTOSCALING_POLICY_NAME); + String output = bout.toString(); + + assertThat(output, CoreMatchers.containsString(CLUSTER_NAME)); + } +} diff --git a/dataproc/src/test/java/InstantiateInlineWorkflowTemplateTest.java b/dataproc/src/test/java/InstantiateInlineWorkflowTemplateTest.java new file mode 100644 index 00000000000..0216ff36bea --- /dev/null +++ b/dataproc/src/test/java/InstantiateInlineWorkflowTemplateTest.java @@ -0,0 +1,63 @@ +/* + * 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. + */ + +import static junit.framework.TestCase.assertNotNull; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import org.hamcrest.CoreMatchers; +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 InstantiateInlineWorkflowTemplateTest { + + private static final String REGION = "us-central1"; + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + + private ByteArrayOutputStream bout; + + private static void requireEnv(String varName) { + assertNotNull( + String.format("Environment variable '%s' is required to perform these tests.", varName), + System.getenv(varName)); + } + + @BeforeClass + public static void checkRequirements() { + requireEnv("GOOGLE_APPLICATION_CREDENTIALS"); + requireEnv("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(bout)); + } + + @Test + public void instanstiateInlineWorkflowTest() throws IOException, InterruptedException { + InstantiateInlineWorkflowTemplate.instantiateInlineWorkflowTemplate(PROJECT_ID, REGION); + String output = bout.toString(); + + assertThat(output, CoreMatchers.containsString("successfully")); + } +} diff --git a/dataproc/src/test/java/QuickstartTest.java b/dataproc/src/test/java/QuickstartTest.java new file mode 100644 index 00000000000..eff7ed05dd3 --- /dev/null +++ b/dataproc/src/test/java/QuickstartTest.java @@ -0,0 +1,117 @@ +/* + * 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. + */ + +import static java.nio.charset.StandardCharsets.UTF_8; +import static junit.framework.TestCase.assertNotNull; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.dataproc.v1.Cluster; +import com.google.cloud.dataproc.v1.ClusterControllerClient; +import com.google.cloud.dataproc.v1.ClusterControllerSettings; +import com.google.cloud.dataproc.v1.ClusterOperationMetadata; +import com.google.cloud.storage.Blob; +import com.google.cloud.storage.Bucket; +import com.google.cloud.storage.BucketInfo; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import com.google.cloud.testing.junit4.StdOutCaptureRule; +import com.google.protobuf.Empty; +import java.io.IOException; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import org.hamcrest.CoreMatchers; +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 QuickstartTest { + + private static final String MY_UUID = UUID.randomUUID().toString(); + private static final String REGION = "us-central1"; + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String ENDPOINT = String.format("%s-dataproc.googleapis.com:443", REGION); + private static final String CLUSTER_NAME = String.format("java-qs-test-%s", MY_UUID); + private static final String BUCKET_NAME = String.format("java-dataproc-qs-test-%s", MY_UUID); + private static final String JOB_FILE_NAME = "sum.py"; + private static final String JOB_FILE_PATH = + String.format("gs://%s/%s", BUCKET_NAME, JOB_FILE_NAME); + private static final String SORT_CODE = + "import pyspark\n" + + "sc = pyspark.SparkContext()\n" + + "rdd = sc.parallelize((1,2,3,4,5))\n" + + "sum = rdd.reduce(lambda x, y: x + y)\n"; + + @Rule public StdOutCaptureRule stdOutCapture = new StdOutCaptureRule(); + private Bucket bucket; + private Blob blob; + + private static void requireEnv(String varName) { + assertNotNull( + String.format("Environment variable '%s' is required to perform these tests.", varName), + System.getenv(varName)); + } + + @BeforeClass + public static void checkRequirements() { + requireEnv("GOOGLE_APPLICATION_CREDENTIALS"); + requireEnv("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() { + Storage storage = StorageOptions.getDefaultInstance().getService(); + bucket = storage.create(BucketInfo.of(BUCKET_NAME)); + blob = bucket.create(JOB_FILE_NAME, SORT_CODE.getBytes(UTF_8), "text/plain"); + } + + @Test + public void quickstartTest() throws IOException, InterruptedException { + Quickstart.main(PROJECT_ID, REGION, CLUSTER_NAME, JOB_FILE_PATH); + String output = stdOutCapture.getCapturedOutputAsUtf8String(); + + assertThat(output, CoreMatchers.containsString("Cluster created successfully")); + assertThat(output, CoreMatchers.containsString("Job finished successfully:")); + assertThat(output, CoreMatchers.containsString("successfully deleted")); + } + + @After + public void teardown() throws IOException, InterruptedException, ExecutionException { + blob.delete(); + bucket.delete(); + + ClusterControllerSettings clusterControllerSettings = + ClusterControllerSettings.newBuilder().setEndpoint(ENDPOINT).build(); + + try (ClusterControllerClient clusterControllerClient = + ClusterControllerClient.create(clusterControllerSettings)) { + for (Cluster element : + clusterControllerClient.listClusters(PROJECT_ID, REGION).iterateAll()) { + if (element.getClusterName() == CLUSTER_NAME) { + OperationFuture deleteClusterAsyncRequest = + clusterControllerClient.deleteClusterAsync(PROJECT_ID, REGION, CLUSTER_NAME); + deleteClusterAsyncRequest.get(); + break; + } + } + } + } +} diff --git a/dataproc/src/test/java/SubmitHadoopFsJobTest.java b/dataproc/src/test/java/SubmitHadoopFsJobTest.java new file mode 100644 index 00000000000..341a3aab7ac --- /dev/null +++ b/dataproc/src/test/java/SubmitHadoopFsJobTest.java @@ -0,0 +1,121 @@ +/* + * 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. + */ + +import static junit.framework.TestCase.assertNotNull; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.dataproc.v1.Cluster; +import com.google.cloud.dataproc.v1.ClusterConfig; +import com.google.cloud.dataproc.v1.ClusterControllerClient; +import com.google.cloud.dataproc.v1.ClusterControllerSettings; +import com.google.cloud.dataproc.v1.ClusterOperationMetadata; +import com.google.cloud.dataproc.v1.InstanceGroupConfig; +import com.google.protobuf.Empty; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import org.hamcrest.CoreMatchers; +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 SubmitHadoopFsJobTest { + + private static final String CLUSTER_NAME = + String.format("java-fs-test--%s", UUID.randomUUID().toString()); + private static final String REGION = "us-central1"; + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String ENDPOINT = String.format("%s-dataproc.googleapis.com:443", REGION); + private static final String HADOOP_FS_QUERY = "-ls /"; + + private ByteArrayOutputStream bout; + + private static void requireEnv(String varName) { + assertNotNull( + String.format("Environment variable '%s' is required to perform these tests.", varName), + System.getenv(varName)); + } + + @BeforeClass + public static void checkRequirements() { + requireEnv("GOOGLE_APPLICATION_CREDENTIALS"); + requireEnv("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() throws IOException, ExecutionException, InterruptedException { + bout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(bout)); + + ClusterControllerSettings clusterControllerSettings = + ClusterControllerSettings.newBuilder().setEndpoint(ENDPOINT).build(); + + try (ClusterControllerClient clusterControllerClient = + ClusterControllerClient.create(clusterControllerSettings)) { + // Configure the settings for our cluster. + InstanceGroupConfig masterConfig = + InstanceGroupConfig.newBuilder() + .setMachineTypeUri("n1-standard-2") + .setNumInstances(1) + .build(); + InstanceGroupConfig workerConfig = + InstanceGroupConfig.newBuilder() + .setMachineTypeUri("n1-standard-2") + .setNumInstances(2) + .build(); + ClusterConfig clusterConfig = + ClusterConfig.newBuilder() + .setMasterConfig(masterConfig) + .setWorkerConfig(workerConfig) + .build(); + // Create the Dataproc cluster. + Cluster cluster = + Cluster.newBuilder().setClusterName(CLUSTER_NAME).setConfig(clusterConfig).build(); + OperationFuture createClusterAsyncRequest = + clusterControllerClient.createClusterAsync(PROJECT_ID, REGION, cluster); + createClusterAsyncRequest.get(); + } + } + + @Test + public void submitHadoopFsJobTest() throws IOException, InterruptedException { + SubmitHadoopFsJob.submitHadoopFsJob(PROJECT_ID, REGION, CLUSTER_NAME, HADOOP_FS_QUERY); + String output = bout.toString(); + + assertThat(output, CoreMatchers.containsString("/tmp")); + } + + @After + public void tearDown() throws IOException, InterruptedException, ExecutionException { + + ClusterControllerSettings clusterControllerSettings = + ClusterControllerSettings.newBuilder().setEndpoint(ENDPOINT).build(); + + try (ClusterControllerClient clusterControllerClient = + ClusterControllerClient.create(clusterControllerSettings)) { + OperationFuture deleteClusterAsyncRequest = + clusterControllerClient.deleteClusterAsync(PROJECT_ID, REGION, CLUSTER_NAME); + deleteClusterAsyncRequest.get(); + } + } +} diff --git a/dataproc/src/test/java/SubmitJobTest.java b/dataproc/src/test/java/SubmitJobTest.java new file mode 100644 index 00000000000..837a4afcb0e --- /dev/null +++ b/dataproc/src/test/java/SubmitJobTest.java @@ -0,0 +1,120 @@ +/* + * 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. + */ + +import static junit.framework.TestCase.assertNotNull; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.dataproc.v1.Cluster; +import com.google.cloud.dataproc.v1.ClusterConfig; +import com.google.cloud.dataproc.v1.ClusterControllerClient; +import com.google.cloud.dataproc.v1.ClusterControllerSettings; +import com.google.cloud.dataproc.v1.ClusterOperationMetadata; +import com.google.cloud.dataproc.v1.InstanceGroupConfig; +import com.google.protobuf.Empty; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import org.hamcrest.CoreMatchers; +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 SubmitJobTest { + + private static final String CLUSTER_NAME = + String.format("java-sj-test--%s", UUID.randomUUID().toString()); + private static final String REGION = "us-central1"; + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String ENDPOINT = String.format("%s-dataproc.googleapis.com:443", REGION); + + private ByteArrayOutputStream bout; + + private static void requireEnv(String varName) { + assertNotNull( + String.format("Environment variable '%s' is required to perform these tests.", varName), + System.getenv(varName)); + } + + @BeforeClass + public static void checkRequirements() { + requireEnv("GOOGLE_APPLICATION_CREDENTIALS"); + requireEnv("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() throws IOException, ExecutionException, InterruptedException { + bout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(bout)); + + ClusterControllerSettings clusterControllerSettings = + ClusterControllerSettings.newBuilder().setEndpoint(ENDPOINT).build(); + + try (ClusterControllerClient clusterControllerClient = + ClusterControllerClient.create(clusterControllerSettings)) { + // Configure the settings for our cluster. + InstanceGroupConfig masterConfig = + InstanceGroupConfig.newBuilder() + .setMachineTypeUri("n1-standard-2") + .setNumInstances(1) + .build(); + InstanceGroupConfig workerConfig = + InstanceGroupConfig.newBuilder() + .setMachineTypeUri("n1-standard-2") + .setNumInstances(2) + .build(); + ClusterConfig clusterConfig = + ClusterConfig.newBuilder() + .setMasterConfig(masterConfig) + .setWorkerConfig(workerConfig) + .build(); + // Create the Dataproc cluster. + Cluster cluster = + Cluster.newBuilder().setClusterName(CLUSTER_NAME).setConfig(clusterConfig).build(); + OperationFuture createClusterAsyncRequest = + clusterControllerClient.createClusterAsync(PROJECT_ID, REGION, cluster); + createClusterAsyncRequest.get(); + } + } + + @Test + public void submitJobTest() throws IOException, InterruptedException { + SubmitJob.submitJob(PROJECT_ID, REGION, CLUSTER_NAME); + String output = bout.toString(); + + assertThat(output, CoreMatchers.containsString("Job finished successfully")); + } + + @After + public void tearDown() throws IOException, InterruptedException, ExecutionException { + + ClusterControllerSettings clusterControllerSettings = + ClusterControllerSettings.newBuilder().setEndpoint(ENDPOINT).build(); + + try (ClusterControllerClient clusterControllerClient = + ClusterControllerClient.create(clusterControllerSettings)) { + OperationFuture deleteClusterAsyncRequest = + clusterControllerClient.deleteClusterAsync(PROJECT_ID, REGION, CLUSTER_NAME); + deleteClusterAsyncRequest.get(); + } + } +} diff --git a/dialogflow-cx/pom.xml b/dialogflow-cx/pom.xml new file mode 100644 index 00000000000..ab51bbfb446 --- /dev/null +++ b/dialogflow-cx/pom.xml @@ -0,0 +1,68 @@ + + + 4.0.0 + com.example.dialogflow-cx + dialogflow-cx-snippets + jar + Google Dialogflow CX Snippets + https://github.com/GoogleCloudPlatform/java-docs-samples/tree/main/dialogflow-cx + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + 11 + 11 + UTF-8 + + + + + + com.google.cloud + google-cloud-dialogflow-cx + 0.14.7 + + + com.google.code.gson + gson + 2.9.1 + + + com.google.cloud.functions + functions-framework-api + 1.0.4 + + + junit + junit + 4.13.2 + test + + + com.google.cloud.functions + function-maven-plugin + 0.10.1 + test + + + org.mockito + mockito-core + 4.8.0 + test + + + com.google.truth + truth + 1.1.3 + test + + + diff --git a/dialogflow-cx/resources/book_a_room.wav b/dialogflow-cx/resources/book_a_room.wav new file mode 100644 index 00000000000..9124e927946 Binary files /dev/null and b/dialogflow-cx/resources/book_a_room.wav differ diff --git a/dialogflow-cx/src/main/java/dialogflow/cx/ConfigureWebhookToSetFormParametersAsOptionalOrRequired.java b/dialogflow-cx/src/main/java/dialogflow/cx/ConfigureWebhookToSetFormParametersAsOptionalOrRequired.java new file mode 100644 index 00000000000..b94c55242ba --- /dev/null +++ b/dialogflow-cx/src/main/java/dialogflow/cx/ConfigureWebhookToSetFormParametersAsOptionalOrRequired.java @@ -0,0 +1,81 @@ +/* + * 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 dialogflow.cx; + +// The following snippet is used in https://cloud.google.com/dialogflow/cx/docs/concept/webhook + +// [START dialogflow_cx_v3_configure_webhooks_to_set_form_parameter_as_optional_or_required] + +// TODO: Change class name to Example +// TODO: Uncomment the line below before running cloud function +// package com.example; + +import com.google.cloud.functions.HttpFunction; +import com.google.cloud.functions.HttpRequest; +import com.google.cloud.functions.HttpResponse; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import java.io.BufferedWriter; + +public class ConfigureWebhookToSetFormParametersAsOptionalOrRequired implements HttpFunction { + @Override + public void service(HttpRequest request, HttpResponse response) throws Exception { + JsonObject parameterObject = new JsonObject(); + parameterObject.addProperty("display_name", "order_number"); + parameterObject.addProperty("required", "true"); + parameterObject.addProperty("state", "VALID"); + + JsonArray parameterInfoList = new JsonArray(); + parameterInfoList.add(parameterObject); + + JsonObject parameterInfoObject = new JsonObject(); + parameterInfoObject.add("parameter_info", parameterInfoList); + + JsonObject formInfo = new JsonObject(); + formInfo.add("form_info", parameterInfoObject); + + // Constructs the webhook response object + JsonObject webhookResponse = new JsonObject(); + webhookResponse.add("page_info", formInfo); + + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + String jsonResponseObject = gson.toJson(webhookResponse); + + /* { + * "page_info": { + * "form_info": { + * "parameter_info": [ + * { + * "display_name": "order_number", + * "required": "true", + * "state": "VALID" + * } + * ] + * } + * } + * } + */ + + BufferedWriter writer = response.getWriter(); + + // Sends the responseObject + writer.write(jsonResponseObject.toString()); + } +} +// [END dialogflow_cx_v3_configure_webhooks_to_set_form_parameter_as_optional_or_required] diff --git a/dialogflow-cx/src/main/java/dialogflow/cx/CreateAgent.java b/dialogflow-cx/src/main/java/dialogflow/cx/CreateAgent.java new file mode 100644 index 00000000000..657abed0ee4 --- /dev/null +++ b/dialogflow-cx/src/main/java/dialogflow/cx/CreateAgent.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dialogflow.cx; + +// [START dialogflow_cx_create_agent] + +import com.google.cloud.dialogflow.cx.v3.Agent; +import com.google.cloud.dialogflow.cx.v3.Agent.Builder; +import com.google.cloud.dialogflow.cx.v3.AgentsClient; +import com.google.cloud.dialogflow.cx.v3.AgentsSettings; +import java.io.IOException; + +public class CreateAgent { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my-project-id"; + String displayName = "my-display-name"; + + createAgent(projectId, displayName); + } + + public static Agent createAgent(String parent, String displayName) throws IOException { + + String apiEndpoint = "global-dialogflow.googleapis.com:443"; + + AgentsSettings agentsSettings = AgentsSettings.newBuilder().setEndpoint(apiEndpoint).build(); + // Note: close() needs to be called on the AgentsClient object to clean up resources + // such as threads. In the example below, try-with-resources is used, + // which automatically calls close(). + try (AgentsClient client = AgentsClient.create(agentsSettings)) { + // Set the details of the Agent to create + Builder build = Agent.newBuilder(); + + build.setDefaultLanguageCode("en"); + build.setDisplayName(displayName); + // Correct format for timezone is location/city + // For example America/Los_Angeles, Europe/Madrid, Asia/Tokyo + build.setTimeZone("America/Los_Angeles"); + + Agent agent = build.build(); + String parentPath = String.format("projects/%s/locations/%s", parent, "global"); + + // Calls the create agent api and returns the created Agent + Agent response = client.createAgent(parentPath, agent); + System.out.println(response); + return response; + } + } +} +// [END dialogflow_cx_create_agent] diff --git a/dialogflow-cx/src/main/java/dialogflow/cx/CreateFlow.java b/dialogflow-cx/src/main/java/dialogflow/cx/CreateFlow.java new file mode 100644 index 00000000000..1719f152639 --- /dev/null +++ b/dialogflow-cx/src/main/java/dialogflow/cx/CreateFlow.java @@ -0,0 +1,93 @@ +/* + * 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 dialogflow.cx; + +// [START dialogflow_cx_create_flow] + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.dialogflow.cx.v3beta1.AgentName; +import com.google.cloud.dialogflow.cx.v3beta1.EventHandler; +import com.google.cloud.dialogflow.cx.v3beta1.Flow; +import com.google.cloud.dialogflow.cx.v3beta1.FlowsClient; +import com.google.cloud.dialogflow.cx.v3beta1.FlowsSettings; +import com.google.cloud.dialogflow.cx.v3beta1.Fulfillment; +import com.google.cloud.dialogflow.cx.v3beta1.ResponseMessage; +import com.google.cloud.dialogflow.cx.v3beta1.ResponseMessage.Text; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class CreateFlow { + + // Create a flow in the specified agent. + public static Flow createFlow( + String displayName, + String projectId, + String locationId, + String agentId, + Map eventsToFulfillmentMessages) + throws IOException, ApiException { + FlowsSettings.Builder flowsSettingsBuilder = FlowsSettings.newBuilder(); + if (locationId.equals("global")) { + flowsSettingsBuilder.setEndpoint("dialogflow.googleapis.com:443"); + } else { + flowsSettingsBuilder.setEndpoint(locationId + "-dialogflow.googleapis.com:443"); + } + FlowsSettings flowsSettings = flowsSettingsBuilder.build(); + + // Instantiates a client. + // Note: close() needs to be called on the FlowsClient object to clean up resources + // such as threads. In the example below, try-with-resources is used, + // which automatically calls close(). + try (FlowsClient flowsClient = FlowsClient.create(flowsSettings)) { + // Set the project agent name using the projectID (my-project-id), locationID (global), and + // agentID (UUID). + AgentName parent = AgentName.of(projectId, locationId, agentId); + + // Build the EventHandlers for the flow using the mapping from events to fulfillment messages. + List eventHandlers = new ArrayList<>(); + for (Map.Entry item : eventsToFulfillmentMessages.entrySet()) { + eventHandlers.add( + EventHandler.newBuilder() + .setEvent(item.getKey()) // Event (sys.no-match-default) + .setTriggerFulfillment( + Fulfillment.newBuilder() + // Text ("Sorry, could you say that again?") + .addMessages( + ResponseMessage.newBuilder() + .setText(Text.newBuilder().addText(item.getValue()).build()) + .build()) + .build()) + .build()); + } + + // Build the flow. + Flow flow = + Flow.newBuilder().setDisplayName(displayName).addAllEventHandlers(eventHandlers).build(); + + // Performs the create flow request. + Flow response = flowsClient.createFlow(parent, flow); + + // TODO : Uncomment if you want to print response + // System.out.format("Flow created: %s\n", response.toString()); + flowsClient.shutdown(); + return response; + } + } +} +// [END dialogflow_cx_create_flow] diff --git a/dialogflow-cx/src/main/java/dialogflow/cx/CreateIntent.java b/dialogflow-cx/src/main/java/dialogflow/cx/CreateIntent.java new file mode 100644 index 00000000000..3469c184853 --- /dev/null +++ b/dialogflow-cx/src/main/java/dialogflow/cx/CreateIntent.java @@ -0,0 +1,86 @@ +/* + * 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 dialogflow.cx; + +// [START dialogflow_cx_create_intent] + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.dialogflow.cx.v3beta1.AgentName; +import com.google.cloud.dialogflow.cx.v3beta1.Intent; +import com.google.cloud.dialogflow.cx.v3beta1.Intent.TrainingPhrase; +import com.google.cloud.dialogflow.cx.v3beta1.Intent.TrainingPhrase.Part; +import com.google.cloud.dialogflow.cx.v3beta1.IntentsClient; +import com.google.cloud.dialogflow.cx.v3beta1.IntentsSettings; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class CreateIntent { + + // Create an intent of the given intent type. + public static Intent createIntent( + String displayName, + String projectId, + String locationId, + String agentId, + List trainingPhrasesParts) + throws IOException, ApiException { + IntentsSettings.Builder intentsSettingsBuilder = IntentsSettings.newBuilder(); + if (locationId.equals("global")) { + intentsSettingsBuilder.setEndpoint("dialogflow.googleapis.com:443"); + } else { + intentsSettingsBuilder.setEndpoint(locationId + "-dialogflow.googleapis.com:443"); + } + IntentsSettings intentsSettings = intentsSettingsBuilder.build(); + + // Instantiates a client + // Note: close() needs to be called on the IntentsClient object to clean up resources + // such as threads. In the example below, try-with-resources is used, + // which automatically calls close(). + try (IntentsClient intentsClient = IntentsClient.create(intentsSettings)) { + // Set the project agent name using the projectID (my-project-id), locationID (global), and + // agentID (UUID). + AgentName parent = AgentName.of(projectId, locationId, agentId); + + // Build the trainingPhrases from the trainingPhrasesParts. + List trainingPhrases = new ArrayList<>(); + for (String trainingPhrase : trainingPhrasesParts) { + trainingPhrases.add( + TrainingPhrase.newBuilder() + .addParts(Part.newBuilder().setText(trainingPhrase).build()) + .setRepeatCount(1) + .build()); + } + + // Build the intent. + Intent intent = + Intent.newBuilder() + .setDisplayName(displayName) + .addAllTrainingPhrases(trainingPhrases) + .build(); + + // Performs the create intent request. + Intent response = intentsClient.createIntent(parent, intent); + + // TODO : Uncomment if you want to print response + // System.out.format("Intent created: %s\n", response); + + return response; + } + } +} +// [END dialogflow_cx_create_intent] diff --git a/dialogflow-cx/src/main/java/dialogflow/cx/CreatePage.java b/dialogflow-cx/src/main/java/dialogflow/cx/CreatePage.java new file mode 100644 index 00000000000..b45bc46f81f --- /dev/null +++ b/dialogflow-cx/src/main/java/dialogflow/cx/CreatePage.java @@ -0,0 +1,116 @@ +/* + * 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 dialogflow.cx; + +// [START dialogflow_cx_create_page] + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.dialogflow.cx.v3beta1.FlowName; +import com.google.cloud.dialogflow.cx.v3beta1.Form; +import com.google.cloud.dialogflow.cx.v3beta1.Form.Parameter; +import com.google.cloud.dialogflow.cx.v3beta1.Form.Parameter.FillBehavior; +import com.google.cloud.dialogflow.cx.v3beta1.Fulfillment; +import com.google.cloud.dialogflow.cx.v3beta1.Page; +import com.google.cloud.dialogflow.cx.v3beta1.PagesClient; +import com.google.cloud.dialogflow.cx.v3beta1.PagesSettings; +import com.google.cloud.dialogflow.cx.v3beta1.ResponseMessage; +import com.google.cloud.dialogflow.cx.v3beta1.ResponseMessage.Text; +import java.io.IOException; +import java.util.List; + +public class CreatePage { + + // Create a page in the specified agent. + public static Page createPage( + String displayName, + String projectId, + String locationId, + String agentId, + String flowId, + List entryTexts) + throws IOException, ApiException { + PagesSettings.Builder pagesSettingsBuilder = PagesSettings.newBuilder(); + if (locationId.equals("global")) { + pagesSettingsBuilder.setEndpoint("dialogflow.googleapis.com:443"); + } else { + pagesSettingsBuilder.setEndpoint(locationId + "-dialogflow.googleapis.com:443"); + } + PagesSettings pagesSettings = pagesSettingsBuilder.build(); + + // Instantiates a client + // Note: close() needs to be called on the PagesClient object to clean up resources + // such as threads. In the example below, try-with-resources is used, + // which automatically calls close(). + try (PagesClient pagesClient = PagesClient.create(pagesSettings)) { + // Set the flow name using the projectID (my-project-id), locationID (global), agentID (UUID) + // and flowID (UUID). + FlowName parent = FlowName.of(projectId, locationId, agentId, flowId); + + // Build the entry fulfillment based on entry texts. + Fulfillment.Builder entryFulfillmentBuilder = Fulfillment.newBuilder(); + for (String entryText : entryTexts) { + entryFulfillmentBuilder.addMessages( + ResponseMessage.newBuilder() + // Text ("Hi") + .setText(Text.newBuilder().addText(entryText).build()) + .build()); + } + Fulfillment entryFulfillment = entryFulfillmentBuilder.build(); + + // Build the form for the new page. + // Note: hard coding parameters for simplicity. + FillBehavior fillBehavior = + FillBehavior.newBuilder() + .setInitialPromptFulfillment( + Fulfillment.newBuilder() + .addMessages( + ResponseMessage.newBuilder() + .setText(Text.newBuilder().addText("What would you like?").build()) + .build()) + .build()) + .build(); + Form form = + Form.newBuilder() + .addParameters( + Parameter.newBuilder() + .setDisplayName("param") + .setRequired(true) + .setEntityType("projects/-/locations/-/agents/-/entityTypes/sys.any") + .setFillBehavior(fillBehavior) + .build()) + .build(); + + // Build the page. + Page page = + Page.newBuilder() + .setDisplayName(displayName) + .setEntryFulfillment(entryFulfillment) + .setForm(form) + .build(); + + // Performs the create page request. + Page response = pagesClient.createPage(parent, page); + + // TODO : Uncomment if you want to print response + // System.out.format("Page created: %s\n", response.toString()); + + pagesClient.shutdown(); + return response; + } + } +} +// [END dialogflow_cx_create_page] diff --git a/dialogflow-cx/src/main/java/dialogflow/cx/CreateSimplePage.java b/dialogflow-cx/src/main/java/dialogflow/cx/CreateSimplePage.java new file mode 100644 index 00000000000..26b10e85422 --- /dev/null +++ b/dialogflow-cx/src/main/java/dialogflow/cx/CreateSimplePage.java @@ -0,0 +1,72 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dialogflow.cx; + +// [START dialogflow_cx_create_page] +import com.google.cloud.dialogflow.cx.v3.CreatePageRequest; +import com.google.cloud.dialogflow.cx.v3.Page; +import com.google.cloud.dialogflow.cx.v3.PagesClient; +import java.io.IOException; + +public class CreateSimplePage { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my-project-id"; + String agentId = "my-agent-id"; + String flowId = "my-flow-id"; + String location = "my-location"; + String displayName = "my-display-name"; + + createPage(projectId, agentId, flowId, location, displayName); + } + + // DialogFlow API Create Page Sample. + // Creates a page from the provided parameters + public static Page createPage( + String projectId, String agentId, String flowId, String location, String displayName) + throws IOException { + Page response; + CreatePageRequest.Builder createRequestBuilder = CreatePageRequest.newBuilder(); + Page.Builder pageBuilder = Page.newBuilder(); + + pageBuilder.setDisplayName(displayName); + + createRequestBuilder + .setParent( + "projects/" + + projectId + + "/locations/" + + location + + "/agents/" + + agentId + + "/flows/" + + flowId) + .setPage(pageBuilder); + + // Make API request to create page + // Note: close() needs to be called on the PagesClient object to clean up resources + // such as threads. In the example below, try-with-resources is used, + // which automatically calls close(). + try (PagesClient client = PagesClient.create()) { + response = client.createPage(createRequestBuilder.build()); + System.out.println("Successfully created page!"); + return response; + } + } + // [END dialogflow_cx_create_page] +} diff --git a/dialogflow-cx/src/main/java/dialogflow/cx/DeletePage.java b/dialogflow-cx/src/main/java/dialogflow/cx/DeletePage.java new file mode 100644 index 00000000000..8403a690a99 --- /dev/null +++ b/dialogflow-cx/src/main/java/dialogflow/cx/DeletePage.java @@ -0,0 +1,68 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dialogflow.cx; + +// [START dialogflow_cx_delete_page] +import com.google.cloud.dialogflow.cx.v3.DeletePageRequest; +import com.google.cloud.dialogflow.cx.v3.DeletePageRequest.Builder; +import com.google.cloud.dialogflow.cx.v3.PagesClient; +import java.io.IOException; + +public class DeletePage { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my-project-id"; + String agentId = "my-agent-id"; + String flowId = "my-flow-id"; + String pageId = "my-page-id"; + String location = "my-location"; + + deletePage(projectId, agentId, flowId, pageId, location); + } + + // DialogFlow API Delete Page Sample. + // Deletes a page from the provided parameters + public static void deletePage( + String projectId, String agentId, String flowId, String pageId, String location) + throws IOException { + + // Note: close() needs to be called on the PagesClient object to clean up resources + // such as threads. In the example below, try-with-resources is used, + // which automatically calls close(). + try (PagesClient client = PagesClient.create()) { + Builder deleteRequestBuilder = DeletePageRequest.newBuilder(); + + deleteRequestBuilder.setName( + "projects/" + + projectId + + "/locations/" + + location + + "/agents/" + + agentId + + "/flows/" + + flowId + + "/pages/" + + pageId); + + // Make API request to delete page + client.deletePage(deleteRequestBuilder.build()); + System.out.println("Successfully deleted page!"); + } + } + // [END dialogflow_cx_delete_page] +} diff --git a/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntent.java b/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntent.java new file mode 100644 index 00000000000..3109a4fc5a8 --- /dev/null +++ b/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntent.java @@ -0,0 +1,105 @@ +/* + * 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 dialogflow.cx; + +// [START dialogflow_cx_detect_intent_text] + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.dialogflow.cx.v3beta1.DetectIntentRequest; +import com.google.cloud.dialogflow.cx.v3beta1.DetectIntentResponse; +import com.google.cloud.dialogflow.cx.v3beta1.QueryInput; +import com.google.cloud.dialogflow.cx.v3beta1.QueryResult; +import com.google.cloud.dialogflow.cx.v3beta1.SessionName; +import com.google.cloud.dialogflow.cx.v3beta1.SessionsClient; +import com.google.cloud.dialogflow.cx.v3beta1.SessionsSettings; +import com.google.cloud.dialogflow.cx.v3beta1.TextInput; +import com.google.common.collect.Maps; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class DetectIntent { + + // DialogFlow API Detect Intent sample with text inputs. + public static Map detectIntent( + String projectId, + String locationId, + String agentId, + String sessionId, + List texts, + String languageCode) + throws IOException, ApiException { + SessionsSettings.Builder sessionsSettingsBuilder = SessionsSettings.newBuilder(); + if (locationId.equals("global")) { + sessionsSettingsBuilder.setEndpoint("dialogflow.googleapis.com:443"); + } else { + sessionsSettingsBuilder.setEndpoint(locationId + "-dialogflow.googleapis.com:443"); + } + SessionsSettings sessionsSettings = sessionsSettingsBuilder.build(); + + Map queryResults = Maps.newHashMap(); + // Instantiates a client. + + // Note: close() needs to be called on the SessionsClient object to clean up resources + // such as threads. In the example below, try-with-resources is used, + // which automatically calls close(). + try (SessionsClient sessionsClient = SessionsClient.create(sessionsSettings)) { + // Set the session name using the projectID (my-project-id), locationID (global), agentID + // (UUID), and sessionId (UUID). + SessionName session = + SessionName.ofProjectLocationAgentSessionName(projectId, locationId, agentId, sessionId); + + // TODO : Uncomment if you want to print session path + // System.out.println("Session Path: " + session.toString()); + + // Detect intents for each text input. + for (String text : texts) { + // Set the text (hello) for the query. + TextInput.Builder textInput = TextInput.newBuilder().setText(text); + + // Build the query with the TextInput and language code (en-US). + QueryInput queryInput = + QueryInput.newBuilder().setText(textInput).setLanguageCode(languageCode).build(); + + // Build the DetectIntentRequest with the SessionName and QueryInput. + DetectIntentRequest request = + DetectIntentRequest.newBuilder() + .setSession(session.toString()) + .setQueryInput(queryInput) + .build(); + + // Performs the detect intent request. + DetectIntentResponse response = sessionsClient.detectIntent(request); + + // Display the query result. + QueryResult queryResult = response.getQueryResult(); + + // TODO : Uncomment if you want to print queryResult + // System.out.println("===================="); + // System.out.format("Query Text: '%s'\n", queryResult.getText()); + // System.out.format( + // "Detected Intent: %s (confidence: %f)\n", + // queryResult.getIntent().getDisplayName(), + // queryResult.getIntentDetectionConfidence()); + + queryResults.put(text, queryResult); + } + } + return queryResults; + } +} +// [END dialogflow_cx_detect_intent_text] diff --git a/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentAudioInput.java b/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentAudioInput.java new file mode 100644 index 00000000000..36a6a17c727 --- /dev/null +++ b/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentAudioInput.java @@ -0,0 +1,132 @@ +/* + * 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 dialogflow.cx; + +// [START dialogflow_cx_v3_detect_intent_audio_input] + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.dialogflow.cx.v3.AudioEncoding; +import com.google.cloud.dialogflow.cx.v3.AudioInput; +import com.google.cloud.dialogflow.cx.v3.DetectIntentRequest; +import com.google.cloud.dialogflow.cx.v3.DetectIntentResponse; +import com.google.cloud.dialogflow.cx.v3.InputAudioConfig; +import com.google.cloud.dialogflow.cx.v3.QueryInput; +import com.google.cloud.dialogflow.cx.v3.QueryResult; +import com.google.cloud.dialogflow.cx.v3.SessionName; +import com.google.cloud.dialogflow.cx.v3.SessionsClient; +import com.google.cloud.dialogflow.cx.v3.SessionsSettings; +import com.google.protobuf.ByteString; +import java.io.FileInputStream; +import java.io.IOException; + +public class DetectIntentAudioInput { + + // DialogFlow API Detect Intent sample with Audio input. + public static void main(String[] args) throws IOException, ApiException { + /** TODO (developer): replace these values with your own values */ + String projectId = "my-project-id"; + String locationId = "global"; + String agentId = "my-agent-id"; + String audioFileName = "resources/book_a_room.wav"; + int sampleRateHertz = 16000; + /* + * A session ID is a string of at most 36 bytes in size. + * Your system is responsible for generating unique session IDs. + * They can be random numbers, hashed end-user identifiers, + * or any other values that are convenient for you to generate. + */ + String sessionId = "my-UUID"; + String languageCode = "en"; + + detectIntent( + projectId, locationId, agentId, audioFileName, sampleRateHertz, sessionId, languageCode); + } + + public static void detectIntent( + String projectId, + String locationId, + String agentId, + String audioFileName, + int sampleRateHertz, + String sessionId, + String languageCode) + throws IOException, ApiException { + + SessionsSettings.Builder sessionsSettingsBuilder = SessionsSettings.newBuilder(); + if (locationId.equals("global")) { + sessionsSettingsBuilder.setEndpoint("dialogflow.googleapis.com:443"); + } else { + sessionsSettingsBuilder.setEndpoint(locationId + "-dialogflow.googleapis.com:443"); + } + SessionsSettings sessionsSettings = sessionsSettingsBuilder.build(); + + // Instantiates a client by setting the session name. + // Format:`projects//locations//agents//sessions/` + + // Note: close() needs to be called on the SessionsClient object to clean up resources + // such as threads. In the example below, try-with-resources is used, + // which automatically calls close(). + try (SessionsClient sessionsClient = SessionsClient.create(sessionsSettings)) { + SessionName session = + SessionName.ofProjectLocationAgentSessionName(projectId, locationId, agentId, sessionId); + + // TODO : Uncomment if you want to print session path + // System.out.println("Session Path: " + session.toString()); + InputAudioConfig inputAudioConfig = + InputAudioConfig.newBuilder() + .setAudioEncoding(AudioEncoding.AUDIO_ENCODING_LINEAR_16) + .setSampleRateHertz(sampleRateHertz) + .build(); + + try (FileInputStream audioStream = new FileInputStream(audioFileName)) { + // Subsequent requests must **only** contain the audio data. + // Following messages: audio chunks. We just read the file in fixed-size chunks. In reality + // you would split the user input by time. + byte[] buffer = new byte[4096]; + int bytes = audioStream.read(buffer); + AudioInput audioInput = + AudioInput.newBuilder() + .setAudio(ByteString.copyFrom(buffer, 0, bytes)) + .setConfig(inputAudioConfig) + .build(); + QueryInput queryInput = + QueryInput.newBuilder() + .setAudio(audioInput) + .setLanguageCode("en-US") // languageCode = "en-US" + .build(); + + DetectIntentRequest request = + DetectIntentRequest.newBuilder() + .setSession(session.toString()) + .setQueryInput(queryInput) + .build(); + + // Performs the detect intent request. + DetectIntentResponse response = sessionsClient.detectIntent(request); + + // Display the query result. + QueryResult queryResult = response.getQueryResult(); + + System.out.println("===================="); + System.out.format( + "Detected Intent: %s (confidence: %f)\n", + queryResult.getTranscript(), queryResult.getIntentDetectionConfidence()); + } + } + } +} +// [END dialogflow_cx_v3_detect_intent_audio_input] diff --git a/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentDisableWebhook.java b/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentDisableWebhook.java new file mode 100644 index 00000000000..ee97aabdab9 --- /dev/null +++ b/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentDisableWebhook.java @@ -0,0 +1,124 @@ +/* + * 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 dialogflow.cx; + +// [START dialogflow_cx_v3_detect_intent_disable_webhook] + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.dialogflow.cx.v3.DetectIntentRequest; +import com.google.cloud.dialogflow.cx.v3.DetectIntentResponse; +import com.google.cloud.dialogflow.cx.v3.QueryInput; +import com.google.cloud.dialogflow.cx.v3.QueryParameters; +import com.google.cloud.dialogflow.cx.v3.QueryResult; +import com.google.cloud.dialogflow.cx.v3.SessionName; +import com.google.cloud.dialogflow.cx.v3.SessionsClient; +import com.google.cloud.dialogflow.cx.v3.SessionsSettings; +import com.google.cloud.dialogflow.cx.v3.TextInput; +import com.google.common.collect.Maps; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class DetectIntentDisableWebhook { + + public static void main(String[] args) throws IOException, ApiException { + String projectId = "my-project-id"; + String locationId = "global"; + String agentId = "my-agent-id"; + String sessionId = "my-UUID"; + List texts = new ArrayList<>(List.of("my-list", "of-texts")); + String languageCode = "en"; + + detectIntent(projectId, locationId, agentId, sessionId, texts, languageCode); + } + + // DialogFlow API Detect Intent sample with webhook disabled. + public static Map detectIntent( + String projectId, + String locationId, + String agentId, + String sessionId, + List texts, + String languageCode) + throws IOException, ApiException { + SessionsSettings.Builder sessionsSettingsBuilder = SessionsSettings.newBuilder(); + if (locationId.equals("global")) { + sessionsSettingsBuilder.setEndpoint("dialogflow.googleapis.com:443"); + } else { + sessionsSettingsBuilder.setEndpoint(locationId + "-dialogflow.googleapis.com:443"); + } + SessionsSettings sessionsSettings = sessionsSettingsBuilder.build(); + + Map queryResults = Maps.newHashMap(); + + // Instantiates a client by setting the session name. + // Format:`projects//locations//agents//sessions/` + + // Note: close() needs to be called on the SessionsClient object to clean up resources + // such as threads. In the example below, try-with-resources is used, + // which automatically calls close(). + try (SessionsClient sessionsClient = SessionsClient.create(sessionsSettings)) { + SessionName session = + SessionName.ofProjectLocationAgentSessionName(projectId, locationId, agentId, sessionId); + + // TODO : Uncomment if you want to print session path + // System.out.println("Session Path: " + session.toString()); + + // Detect intents for each text input. + for (String text : texts) { + // Set the text (hello) for the query. + TextInput.Builder textInput = TextInput.newBuilder().setText(text); + + // Build the query with the TextInput and language code (en-US). + QueryInput queryInput = + QueryInput.newBuilder().setText(textInput).setLanguageCode(languageCode).build(); + + // Build the query parameters and setDisableWebhook to true. + QueryParameters queryParameters = + QueryParameters.newBuilder().setDisableWebhook(true).build(); + + // Build the DetectIntentRequest with the SessionName, QueryInput, and QueryParameters. + DetectIntentRequest request = + DetectIntentRequest.newBuilder() + .setSession(session.toString()) + .setQueryInput(queryInput) + .setQueryParams(queryParameters) + .build(); + System.out.println(request.toString()); + + // Performs the detect intent request. + DetectIntentResponse response = sessionsClient.detectIntent(request); + + // Display the query result. + QueryResult queryResult = response.getQueryResult(); + + // TODO : Uncomment if you want to print queryResult + // System.out.println("===================="); + // System.out.format("Query Text: '%s'\n", queryResult.getText()); + // System.out.format( + // "Detected Intent: %s (confidence: %f)\n", + // queryResult.getIntent().getDisplayName(), + // queryResult.getIntentDetectionConfidence()); + + queryResults.put(text, queryResult); + } + } + return queryResults; + } +} +// [END dialogflow_cx_v3_detect_intent_disable_webhook] diff --git a/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentEventInput.java b/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentEventInput.java new file mode 100644 index 00000000000..ccd1fe3430c --- /dev/null +++ b/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentEventInput.java @@ -0,0 +1,102 @@ +/* + * 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 dialogflow.cx; + +// [START dialogflow_cx_v3_detect_intent_event_input] + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.dialogflow.cx.v3.DetectIntentRequest; +import com.google.cloud.dialogflow.cx.v3.DetectIntentResponse; +import com.google.cloud.dialogflow.cx.v3.EventInput; +import com.google.cloud.dialogflow.cx.v3.QueryInput; +import com.google.cloud.dialogflow.cx.v3.QueryResult; +import com.google.cloud.dialogflow.cx.v3.SessionName; +import com.google.cloud.dialogflow.cx.v3.SessionsClient; +import com.google.cloud.dialogflow.cx.v3.SessionsSettings; +import java.io.IOException; + +public class DetectIntentEventInput { + + // DialogFlow API Detect Intent sample with Event input. + public static void main(String[] args) throws IOException, ApiException { + String projectId = "my-project-id"; + String locationId = "global"; + String agentId = "my-agent-id"; + String sessionId = "my-UUID"; + String event = "my-event-id"; + String languageCode = "en"; + + detectIntent(projectId, locationId, agentId, sessionId, event, languageCode); + } + + public static void detectIntent( + String projectId, + String locationId, + String agentId, + String sessionId, + String event, + String languageCode) + throws IOException, ApiException { + + SessionsSettings.Builder sessionsSettingsBuilder = SessionsSettings.newBuilder(); + if (locationId.equals("global")) { + sessionsSettingsBuilder.setEndpoint("dialogflow.googleapis.com:443"); + } else { + sessionsSettingsBuilder.setEndpoint(locationId + "-dialogflow.googleapis.com:443"); + } + SessionsSettings sessionsSettings = sessionsSettingsBuilder.build(); + + // Instantiates a client by setting the session name. + // Format:`projects//locations//agents//sessions/` + + // Note: close() needs to be called on the SessionsClient object to clean up resources + // such as threads. In the example below, try-with-resources is used, + // which automatically calls close(). + try (SessionsClient sessionsClient = SessionsClient.create(sessionsSettings)) { + SessionName session = + SessionName.ofProjectLocationAgentSessionName(projectId, locationId, agentId, sessionId); + + // TODO : Uncomment if you want to print session path + // System.out.println("Session Path: " + session.toString()); + + EventInput.Builder eventInput = EventInput.newBuilder().setEvent(event); + + // Build the query with the EventInput and language code (en-US). + QueryInput queryInput = + QueryInput.newBuilder().setEvent(eventInput).setLanguageCode(languageCode).build(); + + // Build the DetectIntentRequest with the SessionName and QueryInput. + DetectIntentRequest request = + DetectIntentRequest.newBuilder() + .setSession(session.toString()) + .setQueryInput(queryInput) + .build(); + + // Performs the detect intent request. + DetectIntentResponse response = sessionsClient.detectIntent(request); + + // Display the query result. + QueryResult queryResult = response.getQueryResult(); + + // TODO : Uncomment if you want to print queryResult + System.out.println("===================="); + System.out.format("Triggering Event: %s \n", queryResult.getTriggerEvent()); + } + } +} + +// [END dialogflow_cx_v3_detect_intent_event_input] diff --git a/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentIntentInput.java b/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentIntentInput.java new file mode 100644 index 00000000000..a0e89547db6 --- /dev/null +++ b/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentIntentInput.java @@ -0,0 +1,104 @@ +/* + * 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 dialogflow.cx; + +// [START dialogflow_cx_v3_detect_intent_intent_input] + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.dialogflow.cx.v3.DetectIntentRequest; +import com.google.cloud.dialogflow.cx.v3.DetectIntentResponse; +import com.google.cloud.dialogflow.cx.v3.IntentInput; +import com.google.cloud.dialogflow.cx.v3.QueryInput; +import com.google.cloud.dialogflow.cx.v3.QueryResult; +import com.google.cloud.dialogflow.cx.v3.SessionName; +import com.google.cloud.dialogflow.cx.v3.SessionsClient; +import com.google.cloud.dialogflow.cx.v3.SessionsSettings; +import java.io.IOException; + +public class DetectIntentIntentInput { + + // DialogFlow API Detect Intent sample with Intent input. + public static void main(String[] args) throws IOException, ApiException { + String projectId = "my-project-id"; + String locationId = "global"; + String agentId = "my-agent-id"; + String sessionId = "my-UUID"; + String intent = "my-intent-id"; + String languageCode = "en"; + + detectIntent(projectId, locationId, agentId, sessionId, intent, languageCode); + } + + public static void detectIntent( + String projectId, + String locationId, + String agentId, + String sessionId, + String intent, + String languageCode) + throws IOException, ApiException { + + SessionsSettings.Builder sessionsSettingsBuilder = SessionsSettings.newBuilder(); + if (locationId.equals("global")) { + sessionsSettingsBuilder.setEndpoint("dialogflow.googleapis.com:443"); + } else { + sessionsSettingsBuilder.setEndpoint(locationId + "-dialogflow.googleapis.com:443"); + } + SessionsSettings sessionsSettings = sessionsSettingsBuilder.build(); + + // Instantiates a client by setting the session name. + // Format:`projects//locations//agents//sessions/` + + // Note: close() needs to be called on the SessionsClient object to clean up resources + // such as threads. In the example below, try-with-resources is used, + // which automatically calls close(). + try (SessionsClient sessionsClient = SessionsClient.create(sessionsSettings)) { + SessionName session = + SessionName.ofProjectLocationAgentSessionName(projectId, locationId, agentId, sessionId); + + // TODO : Uncomment if you want to print session path + // System.out.println("Session Path: " + session.toString()); + + IntentInput.Builder intentInput = IntentInput.newBuilder().setIntent(intent); + + // Build the query with the IntentInput and language code (en-US). + QueryInput queryInput = + QueryInput.newBuilder().setIntent(intentInput).setLanguageCode(languageCode).build(); + + // Build the DetectIntentRequest with the SessionName and QueryInput. + DetectIntentRequest request = + DetectIntentRequest.newBuilder() + .setSession(session.toString()) + .setQueryInput(queryInput) + .build(); + + // Performs the detect intent request. + DetectIntentResponse response = sessionsClient.detectIntent(request); + + // Display the query result. + QueryResult queryResult = response.getQueryResult(); + + // TODO : Uncomment if you want to print queryResult + System.out.println("===================="); + System.out.format( + "Detected Intent: %s (confidence: %f)\n", + queryResult.getIntent().getDisplayName(), queryResult.getIntentDetectionConfidence()); + } + } +} + +// [END dialogflow_cx_v3_detect_intent_intent_input] diff --git a/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentSentimentAnalysis.java b/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentSentimentAnalysis.java new file mode 100644 index 00000000000..f69331bbf29 --- /dev/null +++ b/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentSentimentAnalysis.java @@ -0,0 +1,121 @@ +/* + * 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 dialogflow.cx; + +// [START dialogflow_cx_v3_detect_intent_sentiment_analysis] + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.dialogflow.cx.v3.DetectIntentRequest; +import com.google.cloud.dialogflow.cx.v3.DetectIntentResponse; +import com.google.cloud.dialogflow.cx.v3.QueryInput; +import com.google.cloud.dialogflow.cx.v3.QueryParameters; +import com.google.cloud.dialogflow.cx.v3.QueryResult; +import com.google.cloud.dialogflow.cx.v3.SessionName; +import com.google.cloud.dialogflow.cx.v3.SessionsClient; +import com.google.cloud.dialogflow.cx.v3.SessionsSettings; +import com.google.cloud.dialogflow.cx.v3.TextInput; +import com.google.common.collect.Maps; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class DetectIntentSentimentAnalysis { + + public static void main(String[] args) throws IOException, ApiException { + String projectId = "my-project-id"; + String locationId = "global"; + String agentId = "my-agent-id"; + String sessionId = "my-UUID"; + List texts = new ArrayList<>(List.of("my-list", "of-texts")); + String languageCode = "en"; + + detectIntent(projectId, locationId, agentId, sessionId, texts, languageCode); + } + + // DialogFlow API Detect Intent sample with sentiment analysis. + public static Map detectIntent( + String projectId, + String locationId, + String agentId, + String sessionId, + List texts, + String languageCode) + throws IOException, ApiException { + SessionsSettings.Builder sessionsSettingsBuilder = SessionsSettings.newBuilder(); + if (locationId.equals("global")) { + sessionsSettingsBuilder.setEndpoint("dialogflow.googleapis.com:443"); + } else { + sessionsSettingsBuilder.setEndpoint(locationId + "-dialogflow.googleapis.com:443"); + } + SessionsSettings sessionsSettings = sessionsSettingsBuilder.build(); + + Map queryResults = Maps.newHashMap(); + + // Instantiates a client by setting the session name. + // Format:`projects//locations//agents//sessions/` + + // Note: close() needs to be called on the SessionsClient object to clean up resources + // such as threads. In the example below, try-with-resources is used, + // which automatically calls close(). + try (SessionsClient sessionsClient = SessionsClient.create(sessionsSettings)) { + SessionName session = + SessionName.ofProjectLocationAgentSessionName(projectId, locationId, agentId, sessionId); + + // TODO : Uncomment if you want to print session path + // System.out.println("Session Path: " + session.toString()); + + // Detect intents for each text input. + for (String text : texts) { + // Set the text (hello) for the query. + TextInput.Builder textInput = TextInput.newBuilder().setText(text); + + // Build the query with the TextInput and language code (en-US). + QueryInput queryInput = + QueryInput.newBuilder().setText(textInput).setLanguageCode(languageCode).build(); + + // Build the query parameters to analyze the sentiment of the query. + QueryParameters queryParameters = + QueryParameters.newBuilder().setAnalyzeQueryTextSentiment(true).build(); + + // Build the DetectIntentRequest with the SessionName, QueryInput, and QueryParameters. + DetectIntentRequest request = + DetectIntentRequest.newBuilder() + .setSession(session.toString()) + .setQueryInput(queryInput) + .setQueryParams(queryParameters) + .build(); + + // Performs the detect intent request. + DetectIntentResponse response = sessionsClient.detectIntent(request); + + // Display the query result. + QueryResult queryResult = response.getQueryResult(); + + // TODO : Uncomment if you want to print queryResult + // System.out.println("===================="); + // SentimentAnalysisResult sentimentAnalysisResult = + // queryResult.getSentimentAnalysisResult(); + // Float score = sentimentAnalysisResult.getScore(); + + queryResults.put(text, queryResult); + } + } + return queryResults; + } +} +// [END dialogflow_cx_v3_detect_intent_sentiment_analysis] diff --git a/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentStream.java b/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentStream.java new file mode 100644 index 00000000000..f1d05491e38 --- /dev/null +++ b/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentStream.java @@ -0,0 +1,151 @@ +/* + * 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 dialogflow.cx; + +// [START dialogflow_cx_detect_intent_streaming] + +import com.google.api.gax.rpc.ApiException; +import com.google.api.gax.rpc.BidiStream; +import com.google.cloud.dialogflow.cx.v3beta1.AudioEncoding; +import com.google.cloud.dialogflow.cx.v3beta1.AudioInput; +import com.google.cloud.dialogflow.cx.v3beta1.InputAudioConfig; +import com.google.cloud.dialogflow.cx.v3beta1.OutputAudioConfig; +import com.google.cloud.dialogflow.cx.v3beta1.OutputAudioEncoding; +import com.google.cloud.dialogflow.cx.v3beta1.QueryInput; +import com.google.cloud.dialogflow.cx.v3beta1.QueryResult; +import com.google.cloud.dialogflow.cx.v3beta1.SessionName; +import com.google.cloud.dialogflow.cx.v3beta1.SessionsClient; +import com.google.cloud.dialogflow.cx.v3beta1.SessionsSettings; +import com.google.cloud.dialogflow.cx.v3beta1.SsmlVoiceGender; +import com.google.cloud.dialogflow.cx.v3beta1.StreamingDetectIntentRequest; +import com.google.cloud.dialogflow.cx.v3beta1.StreamingDetectIntentResponse; +import com.google.cloud.dialogflow.cx.v3beta1.SynthesizeSpeechConfig; +import com.google.cloud.dialogflow.cx.v3beta1.VoiceSelectionParams; +import com.google.protobuf.ByteString; +import java.io.FileInputStream; +import java.io.IOException; + +public class DetectIntentStream { + + // DialogFlow API Detect Intent sample with audio files processes as an audio stream. + public static void detectIntentStream( + String projectId, String locationId, String agentId, String sessionId, String audioFilePath) + throws ApiException, IOException { + SessionsSettings.Builder sessionsSettingsBuilder = SessionsSettings.newBuilder(); + if (locationId.equals("global")) { + sessionsSettingsBuilder.setEndpoint("dialogflow.googleapis.com:443"); + } else { + sessionsSettingsBuilder.setEndpoint(locationId + "-dialogflow.googleapis.com:443"); + } + SessionsSettings sessionsSettings = sessionsSettingsBuilder.build(); + + // Instantiates a client by setting the session name. + // Format: `projects//locations//agents//sessions/` + // Using the same `sessionId` between requests allows continuation of the conversation. + + // Note: close() needs to be called on the SessionsClient object to clean up resources + // such as threads. In the example below, try-with-resources is used, + // which automatically calls close(). + try (SessionsClient sessionsClient = SessionsClient.create(sessionsSettings)) { + SessionName session = SessionName.of(projectId, locationId, agentId, sessionId); + + // Instructs the speech recognizer how to process the audio content. + // Note: hard coding audioEncoding and sampleRateHertz for simplicity. + // Audio encoding of the audio content sent in the query request. + InputAudioConfig inputAudioConfig = + InputAudioConfig.newBuilder() + .setAudioEncoding(AudioEncoding.AUDIO_ENCODING_LINEAR_16) + .setSampleRateHertz(16000) // sampleRateHertz = 16000 + .build(); + + // Build the AudioInput with the InputAudioConfig. + AudioInput audioInput = AudioInput.newBuilder().setConfig(inputAudioConfig).build(); + + // Build the query with the InputAudioConfig. + QueryInput queryInput = + QueryInput.newBuilder() + .setAudio(audioInput) + .setLanguageCode("en-US") // languageCode = "en-US" + .build(); + + // Create the Bidirectional stream + BidiStream bidiStream = + sessionsClient.streamingDetectIntentCallable().call(); + + // Specify sssml name and gender + VoiceSelectionParams voiceSelection = + // Voices that are available https://cloud.google.com/text-to-speech/docs/voices + VoiceSelectionParams.newBuilder() + .setName("en-GB-Standard-A") + .setSsmlGender(SsmlVoiceGender.SSML_VOICE_GENDER_FEMALE) + .build(); + + SynthesizeSpeechConfig speechConfig = + SynthesizeSpeechConfig.newBuilder().setVoice(voiceSelection).build(); + + // Setup audio config + OutputAudioConfig audioConfig = + // Output enconding explanation + // https://cloud.google.com/dialogflow/cx/docs/reference/rpc/google.cloud.dialogflow.cx.v3#outputaudioencoding + OutputAudioConfig.newBuilder() + .setAudioEncoding(OutputAudioEncoding.OUTPUT_AUDIO_ENCODING_UNSPECIFIED) + .setAudioEncodingValue(1) + .setSynthesizeSpeechConfig(speechConfig) + .build(); + + // The first request must **only** contain the audio configuration: + bidiStream.send( + StreamingDetectIntentRequest.newBuilder() + .setSession(session.toString()) + .setQueryInput(queryInput) + .setOutputAudioConfig(audioConfig) + .build()); + + try (FileInputStream audioStream = new FileInputStream(audioFilePath)) { + // Subsequent requests must **only** contain the audio data. + // Following messages: audio chunks. We just read the file in fixed-size chunks. In reality + // you would split the user input by time. + byte[] buffer = new byte[4096]; + int bytes; + while ((bytes = audioStream.read(buffer)) != -1) { + AudioInput subAudioInput = + AudioInput.newBuilder().setAudio(ByteString.copyFrom(buffer, 0, bytes)).build(); + QueryInput subQueryInput = + QueryInput.newBuilder() + .setAudio(subAudioInput) + .setLanguageCode("en-US") // languageCode = "en-US" + .build(); + bidiStream.send( + StreamingDetectIntentRequest.newBuilder().setQueryInput(subQueryInput).build()); + } + } + + // Tell the service you are done sending data. + bidiStream.closeSend(); + + for (StreamingDetectIntentResponse response : bidiStream) { + QueryResult queryResult = response.getDetectIntentResponse().getQueryResult(); + System.out.println("===================="); + System.out.format("Query Text: '%s'\n", queryResult.getTranscript()); + System.out.format( + "Detected Intent: %s (confidence: %f)\n", + queryResult.getIntent().getDisplayName(), queryResult.getIntentDetectionConfidence()); + } + } + } +} +// [END dialogflow_cx_detect_intent_streaming] diff --git a/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentStreamingPartialResponse.java b/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentStreamingPartialResponse.java new file mode 100644 index 00000000000..af725804366 --- /dev/null +++ b/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentStreamingPartialResponse.java @@ -0,0 +1,158 @@ +/* + * 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 dialogflow.cx; + +// [START dialogflow_cx_v3_detect_intent_streaming_partial_response] + +import com.google.api.gax.rpc.ApiException; +import com.google.api.gax.rpc.BidiStream; +import com.google.cloud.dialogflow.cx.v3.AudioEncoding; +import com.google.cloud.dialogflow.cx.v3.AudioInput; +import com.google.cloud.dialogflow.cx.v3.InputAudioConfig; +import com.google.cloud.dialogflow.cx.v3.OutputAudioConfig; +import com.google.cloud.dialogflow.cx.v3.OutputAudioEncoding; +import com.google.cloud.dialogflow.cx.v3.QueryInput; +import com.google.cloud.dialogflow.cx.v3.SessionName; +import com.google.cloud.dialogflow.cx.v3.SessionsClient; +import com.google.cloud.dialogflow.cx.v3.SessionsSettings; +import com.google.cloud.dialogflow.cx.v3.SsmlVoiceGender; +import com.google.cloud.dialogflow.cx.v3.StreamingDetectIntentRequest; +import com.google.cloud.dialogflow.cx.v3.StreamingDetectIntentResponse; +import com.google.cloud.dialogflow.cx.v3.SynthesizeSpeechConfig; +import com.google.cloud.dialogflow.cx.v3.VoiceSelectionParams; +import com.google.protobuf.ByteString; +import java.io.FileInputStream; +import java.io.IOException; + +public class DetectIntentStreamingPartialResponse { + + // DialogFlow API Detect Intent sample with audio files + // that processes as an audio stream. + public static void detectIntentStreamingPartialResponse( + String projectId, String locationId, String agentId, String sessionId, String audioFilePath) + throws ApiException, IOException { + SessionsSettings.Builder sessionsSettingsBuilder = SessionsSettings.newBuilder(); + if (locationId.equals("global")) { + sessionsSettingsBuilder.setEndpoint("dialogflow.googleapis.com:443"); + } else { + sessionsSettingsBuilder.setEndpoint(locationId + "-dialogflow.googleapis.com:443"); + } + SessionsSettings sessionsSettings = sessionsSettingsBuilder.build(); + + // Instantiates a client by setting the session name. + // Format:`projects//locations//agents//sessions/` + // Using the same `sessionId` between requests allows continuation of the conversation. + + // Note: close() needs to be called on the SessionsClient object to clean up resources + // such as threads. In the example below, try-with-resources is used, + // which automatically calls close(). + try (SessionsClient sessionsClient = SessionsClient.create(sessionsSettings)) { + SessionName session = SessionName.of(projectId, locationId, agentId, sessionId); + + // Instructs the speech recognizer how to process the audio content. + // Note: hard coding audioEncoding and sampleRateHertz for simplicity. + // Audio encoding of the audio content sent in the query request. + InputAudioConfig inputAudioConfig = + InputAudioConfig.newBuilder() + .setAudioEncoding(AudioEncoding.AUDIO_ENCODING_LINEAR_16) + .setSampleRateHertz(16000) // sampleRateHertz = 16000 + .build(); + + // Build the AudioInput with the InputAudioConfig. + AudioInput audioInput = AudioInput.newBuilder().setConfig(inputAudioConfig).build(); + + // Build the query with the InputAudioConfig. + QueryInput queryInput = + QueryInput.newBuilder() + .setAudio(audioInput) + .setLanguageCode("en-US") // languageCode = "en-US" + .build(); + + // Create the Bidirectional stream + BidiStream bidiStream = + sessionsClient.streamingDetectIntentCallable().call(); + + // Specify sssml name and gender + VoiceSelectionParams voiceSelection = + // Voices that are available https://cloud.google.com/text-to-speech/docs/voices + VoiceSelectionParams.newBuilder() + .setName("en-GB-Standard-A") + .setSsmlGender(SsmlVoiceGender.SSML_VOICE_GENDER_FEMALE) + .build(); + + SynthesizeSpeechConfig speechConfig = + SynthesizeSpeechConfig.newBuilder().setVoice(voiceSelection).build(); + + // Setup audio config + OutputAudioConfig audioConfig = + // Output encoding explanation + // https://cloud.google.com/dialogflow/cx/docs/reference/rpc/google.cloud.dialogflow.cx.v3#outputaudioencoding + OutputAudioConfig.newBuilder() + .setAudioEncoding(OutputAudioEncoding.OUTPUT_AUDIO_ENCODING_UNSPECIFIED) + .setAudioEncodingValue(1) + .setSynthesizeSpeechConfig(speechConfig) + .build(); + + StreamingDetectIntentRequest streamingDetectIntentRequest = + StreamingDetectIntentRequest.newBuilder() + .setSession(session.toString()) + .setQueryInput(queryInput) + .setEnablePartialResponse(true) + .setOutputAudioConfig(audioConfig) + .build(); + System.out.println(streamingDetectIntentRequest.toString()); + + // The first request must **only** contain the audio configuration: + bidiStream.send(streamingDetectIntentRequest); + + try (FileInputStream audioStream = new FileInputStream(audioFilePath)) { + // Subsequent requests must **only** contain the audio data. + // Following messages: audio chunks. We just read the file in fixed-size chunks. In reality + // you would split the user input by time. + byte[] buffer = new byte[4096]; + int bytes; + while ((bytes = audioStream.read(buffer)) != -1) { + AudioInput subAudioInput = + AudioInput.newBuilder().setAudio(ByteString.copyFrom(buffer, 0, bytes)).build(); + QueryInput subQueryInput = + QueryInput.newBuilder() + .setAudio(subAudioInput) + .setLanguageCode("en-US") // languageCode = "en-US" + .build(); + bidiStream.send( + StreamingDetectIntentRequest.newBuilder().setQueryInput(subQueryInput).build()); + } + } + + // Tell the service you are done sending data. + bidiStream.closeSend(); + + // TODO: Uncomment to print detectIntentResponse. + + // for (StreamingDetectIntentResponse response : bidiStream) { + // QueryResult queryResult = response.getDetectIntentResponse().getQueryResult(); + // System.out.println("===================="); + // System.out.format("Query Text: '%s'\n", queryResult.getTranscript()); + // System.out.format( + // "Detected Intent: %s (confidence: %f)\n", + // queryResult.getIntent() + // .getDisplayName(), queryResult.getIntentDetectionConfidence()); + // } + } + } +} +// [END dialogflow_cx_v3_detect_intent_streaming_partial_response] diff --git a/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentSynthesizeTextToSpeechOutput.java b/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentSynthesizeTextToSpeechOutput.java new file mode 100644 index 00000000000..74c155f4d0b --- /dev/null +++ b/dialogflow-cx/src/main/java/dialogflow/cx/DetectIntentSynthesizeTextToSpeechOutput.java @@ -0,0 +1,135 @@ +/* + * 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 dialogflow.cx; + +// [START dialogflow_cx_v3_detect_intent_synthesize_tts_output] + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.dialogflow.cx.v3.AudioEncoding; +import com.google.cloud.dialogflow.cx.v3.AudioInput; +import com.google.cloud.dialogflow.cx.v3.DetectIntentRequest; +import com.google.cloud.dialogflow.cx.v3.DetectIntentResponse; +import com.google.cloud.dialogflow.cx.v3.InputAudioConfig; +import com.google.cloud.dialogflow.cx.v3.OutputAudioConfig; +import com.google.cloud.dialogflow.cx.v3.OutputAudioEncoding; +import com.google.cloud.dialogflow.cx.v3.QueryInput; +import com.google.cloud.dialogflow.cx.v3.SessionName; +import com.google.cloud.dialogflow.cx.v3.SessionsClient; +import com.google.cloud.dialogflow.cx.v3.SessionsSettings; +import com.google.cloud.dialogflow.cx.v3.SynthesizeSpeechConfig; +import com.google.protobuf.ByteString; +import java.io.FileInputStream; +import java.io.IOException; + +public class DetectIntentSynthesizeTextToSpeechOutput { + + // DialogFlow API Detect Intent sample with synthesize TTS output. + public static void main(String[] args) throws IOException, ApiException { + String projectId = "my-project-id"; + String locationId = "my-location-id"; + String agentId = "my-agent-id"; + String audioFileName = "my-audio-file-name"; + int sampleRateHertz = 16000; + String sessionId = "my-session-id"; + String languageCode = "my-language-code"; + + detectIntent( + projectId, locationId, agentId, audioFileName, sampleRateHertz, sessionId, languageCode); + } + + public static void detectIntent( + String projectId, + String locationId, + String agentId, + String audioFileName, + int sampleRateHertz, + String sessionId, + String languageCode) + throws IOException, ApiException { + + SessionsSettings.Builder sessionsSettingsBuilder = SessionsSettings.newBuilder(); + if (locationId.equals("global")) { + sessionsSettingsBuilder.setEndpoint("dialogflow.googleapis.com:443"); + } else { + sessionsSettingsBuilder.setEndpoint(locationId + "-dialogflow.googleapis.com:443"); + } + SessionsSettings sessionsSettings = sessionsSettingsBuilder.build(); + + // Instantiates a client by setting the session name. + // Format:`projects//locations//agents//sessions/` + + // Note: close() needs to be called on the SessionsClient object to clean up resources + // such as threads. In the example below, try-with-resources is used, + // which automatically calls close(). + try (SessionsClient sessionsClient = SessionsClient.create(sessionsSettings)) { + SessionName session = + SessionName.ofProjectLocationAgentSessionName(projectId, locationId, agentId, sessionId); + + // TODO : Uncomment if you want to print session path + // System.out.println("Session Path: " + session.toString()); + InputAudioConfig inputAudioConfig = + InputAudioConfig.newBuilder() + .setAudioEncoding(AudioEncoding.AUDIO_ENCODING_LINEAR_16) + .setSampleRateHertz(sampleRateHertz) + .build(); + + try (FileInputStream audioStream = new FileInputStream(audioFileName)) { + // Subsequent requests must **only** contain the audio data. + // Following messages: audio chunks. We just read the file in fixed-size chunks. In reality + // you would split the user input by time. + byte[] buffer = new byte[4096]; + int bytes = audioStream.read(buffer); + AudioInput audioInput = + AudioInput.newBuilder() + .setAudio(ByteString.copyFrom(buffer, 0, bytes)) + .setConfig(inputAudioConfig) + .build(); + QueryInput queryInput = + QueryInput.newBuilder() + .setAudio(audioInput) + .setLanguageCode("en-US") // languageCode = "en-US" + .build(); + + SynthesizeSpeechConfig speechConfig = + SynthesizeSpeechConfig.newBuilder().setSpeakingRate(1.25).setPitch(10.0).build(); + + OutputAudioConfig outputAudioConfig = + OutputAudioConfig.newBuilder() + .setAudioEncoding(OutputAudioEncoding.OUTPUT_AUDIO_ENCODING_LINEAR_16) + .setSynthesizeSpeechConfig(speechConfig) + .build(); + + DetectIntentRequest request = + DetectIntentRequest.newBuilder() + .setSession(session.toString()) + .setQueryInput(queryInput) + .setOutputAudioConfig(outputAudioConfig) + .build(); + + // Performs the detect intent request. + DetectIntentResponse response = sessionsClient.detectIntent(request); + + // Display the output audio config retrieved from the response. + OutputAudioConfig audioConfigFromResponse = response.getOutputAudioConfig(); + + System.out.println("===================="); + System.out.format("Output Audio Config: %s \n", audioConfigFromResponse.toString()); + } + } + } +} +// [END dialogflow_cx_v3_detect_intent_synthesize_tts_output] diff --git a/dialogflow-cx/src/main/java/dialogflow/cx/ExportAgent.java b/dialogflow-cx/src/main/java/dialogflow/cx/ExportAgent.java new file mode 100644 index 00000000000..6b431f5108c --- /dev/null +++ b/dialogflow-cx/src/main/java/dialogflow/cx/ExportAgent.java @@ -0,0 +1,69 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dialogflow.cx; + +// [START dialogflow_cx_export_agent] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.dialogflow.cx.v3.AgentName; +import com.google.cloud.dialogflow.cx.v3.AgentsClient; +import com.google.cloud.dialogflow.cx.v3.AgentsSettings; +import com.google.cloud.dialogflow.cx.v3.ExportAgentRequest; +import com.google.cloud.dialogflow.cx.v3.ExportAgentResponse; +import com.google.protobuf.Struct; +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +public class ExportAgent { + + public static void main(String[] args) + throws IOException, InterruptedException, ExecutionException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my-project-id"; + String agentId = "my-agent-id"; + String location = "my-location"; + + exportAgent(projectId, agentId, location); + } + + public static void exportAgent(String projectId, String agentId, String location) + throws IOException, InterruptedException, ExecutionException { + + // Sets the api endpoint to specified location + String apiEndpoint = String.format("%s-dialogflow.googleapis.com:443", location); + + AgentsSettings agentsSettings = AgentsSettings.newBuilder().setEndpoint(apiEndpoint).build(); + // Note: close() needs to be called on the AgentsClient object to clean up resources + // such as threads. In the example below, try-with-resources is used, + // which automatically calls close(). + try (AgentsClient agentsClient = AgentsClient.create(agentsSettings)) { + ExportAgentRequest request = + ExportAgentRequest.newBuilder() + .setName(AgentName.of(projectId, location, agentId).toString()) + .build(); + + // Returns a future of the operation + OperationFuture future = + agentsClient.exportAgentOperationCallable().futureCall(request); + + // get the export agent response after the operation is completed + ExportAgentResponse response = future.get(); + System.out.println(response); + } + } +} +// [END dialogflow_cx_export_agent] diff --git a/dialogflow-cx/src/main/java/dialogflow/cx/ListPages.java b/dialogflow-cx/src/main/java/dialogflow/cx/ListPages.java new file mode 100644 index 00000000000..9a180081f67 --- /dev/null +++ b/dialogflow-cx/src/main/java/dialogflow/cx/ListPages.java @@ -0,0 +1,61 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dialogflow.cx; + +// [START dialogflow_cx_list_pages] +import com.google.cloud.dialogflow.cx.v3.ListPagesRequest; +import com.google.cloud.dialogflow.cx.v3.ListPagesRequest.Builder; +import com.google.cloud.dialogflow.cx.v3.Page; +import com.google.cloud.dialogflow.cx.v3.PagesClient; +import java.io.IOException; + +public class ListPages { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my-project-id"; + String agentId = "my-agent-id"; + String flowId = "my-flow-id"; + String location = "my-location"; + + listPages(projectId, agentId, flowId, location); + } + + // DialogFlow API List Pages Sample. + // Lists all pages from the provided parameters + public static void listPages(String projectId, String agentId, String flowId, String location) + throws IOException { + // Note: close() needs to be called on the PagesClient object to clean up resources + // such as threads. In the example below, try-with-resources is used, + // which automatically calls close(). + try (PagesClient client = PagesClient.create()) { + Builder listRequestBuilder = ListPagesRequest.newBuilder(); + + String parentPath = + String.format( + "projects/%s/locations/%s/agents/%s/flows/%s", projectId, location, agentId, flowId); + listRequestBuilder.setParent(parentPath); + listRequestBuilder.setLanguageCode("en"); + + // Make API request to list all pages in the project + for (Page element : client.listPages(listRequestBuilder.build()).iterateAll()) { + System.out.println(element); + } + } + } + // [END dialogflow_cx_list_pages] +} diff --git a/dialogflow-cx/src/main/java/dialogflow/cx/ListTestCaseResults.java b/dialogflow-cx/src/main/java/dialogflow/cx/ListTestCaseResults.java new file mode 100644 index 00000000000..2f34d6530c1 --- /dev/null +++ b/dialogflow-cx/src/main/java/dialogflow/cx/ListTestCaseResults.java @@ -0,0 +1,71 @@ +/* + * 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 dialogflow.cx; + +// [START dialogflow_cx_list_testcase_result_sample] + +import com.google.cloud.dialogflow.cx.v3.ListTestCaseResultsRequest; +import com.google.cloud.dialogflow.cx.v3.ListTestCaseResultsRequest.Builder; +import com.google.cloud.dialogflow.cx.v3.TestCaseResult; +import com.google.cloud.dialogflow.cx.v3.TestCasesClient; +import com.google.cloud.dialogflow.cx.v3.TestCasesSettings; +import java.io.IOException; + +public class ListTestCaseResults { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my-project-id"; + String agentId = "my-agent-id"; + String testId = "my-test-id"; + String location = "my-location"; + listTestCaseResults(projectId, agentId, testId, location); + } + + public static void listTestCaseResults( + String projectId, String agentId, String testId, String location) throws IOException { + String parent = + "projects/" + + projectId + + "/locations/" + + location + + "/agents/" + + agentId + + "/testCases/" + + testId; + + Builder req = ListTestCaseResultsRequest.newBuilder(); + + req.setParent(parent); + req.setFilter("environment=draft"); + + TestCasesSettings testCasesSettings = + TestCasesSettings.newBuilder() + .setEndpoint(location + "-dialogflow.googleapis.com:443") + .build(); + + // Note: close() needs to be called on the TestCasesClient object to clean up resources + // such as threads. In the example below, try-with-resources is used, + // which automatically calls close(). + try (TestCasesClient client = TestCasesClient.create(testCasesSettings)) { + for (TestCaseResult element : client.listTestCaseResults(req.build()).iterateAll()) { + System.out.println(element); + } + } + } +} +// [END dialogflow_cx_list_testcase_result_sample] diff --git a/dialogflow-cx/src/main/java/dialogflow/cx/UpdateIntent.java b/dialogflow-cx/src/main/java/dialogflow/cx/UpdateIntent.java new file mode 100644 index 00000000000..ea16649f66a --- /dev/null +++ b/dialogflow-cx/src/main/java/dialogflow/cx/UpdateIntent.java @@ -0,0 +1,78 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dialogflow.cx; + +// [START dialogflow_cx_update_intent] + +import com.google.cloud.dialogflow.cx.v3.Intent; +import com.google.cloud.dialogflow.cx.v3.Intent.Builder; +import com.google.cloud.dialogflow.cx.v3.IntentsClient; +import com.google.cloud.dialogflow.cx.v3.UpdateIntentRequest; +import com.google.protobuf.FieldMask; +import java.io.IOException; + +public class UpdateIntent { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my-project-id"; + String agentId = "my-agent-id"; + String intentId = "my-intent-id"; + String location = "my-location"; + String displayName = "my-display-name"; + updateIntent(projectId, agentId, intentId, location, displayName); + } + + // DialogFlow API Update Intent sample. + public static void updateIntent( + String projectId, String agentId, String intentId, String location, String displayName) + throws IOException { + + // Note: close() needs to be called on the IntentsClient object to clean up resources + // such as threads. In the example below, try-with-resources is used, + // which automatically calls close(). + try (IntentsClient client = IntentsClient.create()) { + String intentPath = + "projects/" + + projectId + + "/locations/" + + location + + "/agents/" + + agentId + + "/intents/" + + intentId; + + Builder intentBuilder = client.getIntent(intentPath).toBuilder(); + + intentBuilder.setDisplayName(displayName); + FieldMask fieldMask = FieldMask.newBuilder().addPaths("display_name").build(); + + Intent intent = intentBuilder.build(); + UpdateIntentRequest request = + UpdateIntentRequest.newBuilder() + .setIntent(intent) + .setLanguageCode("en") + .setUpdateMask(fieldMask) + .build(); + + // Make API request to update intent using fieldmask + Intent response = client.updateIntent(request); + System.out.println(response); + } + } +} +// [END dialogflow_cx_update_intent] diff --git a/dialogflow-cx/src/main/java/dialogflow/cx/WebhookConfigureSessionParameters.java b/dialogflow-cx/src/main/java/dialogflow/cx/WebhookConfigureSessionParameters.java new file mode 100644 index 00000000000..2d59a8ba7e1 --- /dev/null +++ b/dialogflow-cx/src/main/java/dialogflow/cx/WebhookConfigureSessionParameters.java @@ -0,0 +1,58 @@ +/* + * 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 dialogflow.cx; + +// The following snippet is used in https://cloud.google.com/dialogflow/cx/docs/concept/webhook +// Configures a webhook to set a session parameter + +// [START dialogflow_cx_v3_webhook_configure_session_parameters] + +// TODO: Change class name to Example +// TODO: Uncomment the line below before running cloud function +// package com.example; + +import com.google.cloud.functions.HttpFunction; +import com.google.cloud.functions.HttpRequest; +import com.google.cloud.functions.HttpResponse; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import java.io.BufferedWriter; + +public class WebhookConfigureSessionParameters implements HttpFunction { + @Override + public void service(HttpRequest request, HttpResponse response) throws Exception { + JsonObject orderParameter = new JsonObject(); + orderParameter.addProperty("order_number", "12345"); + + JsonObject parameterObject = new JsonObject(); + parameterObject.add("parameters", orderParameter); + + // Creates webhook response object + JsonObject webhookResponse = new JsonObject(); + webhookResponse.add("session_info", parameterObject); + + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + String jsonResponseObject = gson.toJson(webhookResponse); + + /** { "session_info": { "parameters": { "order_number": "12345" } } } */ + BufferedWriter writer = response.getWriter(); + // Sends the webhookResponseObject + writer.write(jsonResponseObject.toString()); + } +} +// [END dialogflow_cx_v3_webhook_configure_session_parameters] diff --git a/dialogflow-cx/src/main/java/dialogflow/cx/WebhookValidateFormParameter.java b/dialogflow-cx/src/main/java/dialogflow/cx/WebhookValidateFormParameter.java new file mode 100644 index 00000000000..320b57f3d65 --- /dev/null +++ b/dialogflow-cx/src/main/java/dialogflow/cx/WebhookValidateFormParameter.java @@ -0,0 +1,79 @@ +/* + * 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 dialogflow.cx; + +// The following snippet is used in https://cloud.google.com/dialogflow/cx/docs/concept/webhook + +// [START dialogflow_cx_v3_configure_webhooks_to_set_form_parameter_as_optional_or_required] + +// TODO: Change class name to Example +// TODO: Uncomment the line below before running cloud function +// package com.example; + +import com.google.cloud.functions.HttpFunction; +import com.google.cloud.functions.HttpRequest; +import com.google.cloud.functions.HttpResponse; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import java.io.BufferedWriter; + +public class WebhookValidateFormParameter implements HttpFunction { + @Override + public void service(HttpRequest request, HttpResponse response) throws Exception { + JsonObject sessionInfo = new JsonObject(); + JsonObject sessionParameter = new JsonObject(); + + sessionParameter.addProperty("order_number", "null"); + sessionInfo.add("parameters", sessionParameter); + + JsonObject parameterObject = new JsonObject(); + parameterObject.addProperty("display_name", "order_number"); + parameterObject.addProperty("required", "true"); + parameterObject.addProperty("state", "INVALID"); + parameterObject.addProperty("value", "123"); + + JsonArray parameterInfoList = new JsonArray(); + parameterInfoList.add(parameterObject); + + JsonObject parameterInfoObject = new JsonObject(); + parameterInfoObject.add("parameter_info", parameterInfoList); + + JsonObject pageInfo = new JsonObject(); + pageInfo.add("form_info", parameterInfoObject); + + // Constructs the webhook response object + JsonObject webhookResponse = new JsonObject(); + webhookResponse.add("page_info", pageInfo); + webhookResponse.add("session_info", sessionInfo); + + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + String jsonResponseObject = gson.toJson(webhookResponse); + + /** + * { "page_info": { "form_info": { "parameter_info": [ { "display_name": "order_number", + * "required": "true", "state": "INVALID", "value": "123" } ] } }, "session_info": { + * "parameters": { "order_number": "null" } } } + */ + BufferedWriter writer = response.getWriter(); + + // Sends the responseObject + writer.write(jsonResponseObject.toString()); + } +} +// [END dialogflow_cx_v3_configure_webhooks_to_set_form_parameter_as_optional_or_required] diff --git a/dialogflow-cx/src/test/java/dialogflow/cx/ConfigureWebhookToSetFormParametersAsOptionalOrRequiredIT.java b/dialogflow-cx/src/test/java/dialogflow/cx/ConfigureWebhookToSetFormParametersAsOptionalOrRequiredIT.java new file mode 100644 index 00000000000..53e30e094e6 --- /dev/null +++ b/dialogflow-cx/src/test/java/dialogflow/cx/ConfigureWebhookToSetFormParametersAsOptionalOrRequiredIT.java @@ -0,0 +1,101 @@ +/* + * 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 dialogflow.cx; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + +import com.google.cloud.dialogflow.cx.v3beta1.WebhookRequest; +import com.google.cloud.dialogflow.cx.v3beta1.WebhookRequest.FulfillmentInfo; +import com.google.cloud.functions.HttpRequest; +import com.google.cloud.functions.HttpResponse; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class ConfigureWebhookToSetFormParametersAsOptionalOrRequiredIT { + + @Mock HttpRequest request; + @Mock HttpResponse response; + + BufferedReader jsonReader; + StringReader stringReader; + BufferedWriter writerOut; + StringWriter responseOut; + + @Before + public void beforeTest() throws IOException { + MockitoAnnotations.initMocks(this); + + stringReader = new StringReader("{'fulfillmentInfo': {'tag': 'validate-form-parameter'}}"); + jsonReader = new BufferedReader(stringReader); + + responseOut = new StringWriter(); + writerOut = new BufferedWriter(responseOut); + + when(request.getReader()).thenReturn(jsonReader); + when(response.getWriter()).thenReturn(writerOut); + } + + @Test + public void helloHttp_bodyParamsPost() throws IOException, Exception { + + FulfillmentInfo fulfillmentInfo = + FulfillmentInfo.newBuilder() + .setTag("configure-form-parameters-optional-or-parameter") + .build(); + + WebhookRequest webhookRequest = + WebhookRequest.newBuilder().setFulfillmentInfo(fulfillmentInfo).build(); + + new ConfigureWebhookToSetFormParametersAsOptionalOrRequired().service(request, response); + writerOut.flush(); + + JsonObject parameterObject = new JsonObject(); + parameterObject.addProperty("display_name", "order_number"); + parameterObject.addProperty("required", "true"); + parameterObject.addProperty("state", "VALID"); + + JsonArray parameterInfoList = new JsonArray(); + parameterInfoList.add(parameterObject); + + JsonObject parameterInfoObject = new JsonObject(); + parameterInfoObject.add("parameter_info", parameterInfoList); + + JsonObject formInfo = new JsonObject(); + formInfo.add("form_info", parameterInfoObject); + + JsonObject webhookResponse = new JsonObject(); + webhookResponse.add("page_info", formInfo); + + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + String expectedResponse = gson.toJson(webhookResponse); + + assertThat(responseOut.toString()).isEqualTo(expectedResponse); + Thread.sleep(200); + } +} diff --git a/dialogflow-cx/src/test/java/dialogflow/cx/CreateAgentIT.java b/dialogflow-cx/src/test/java/dialogflow/cx/CreateAgentIT.java new file mode 100644 index 00000000000..aee80c7dee1 --- /dev/null +++ b/dialogflow-cx/src/test/java/dialogflow/cx/CreateAgentIT.java @@ -0,0 +1,67 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dialogflow.cx; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.dialogflow.cx.v3.AgentsClient; +import com.google.cloud.dialogflow.cx.v3.AgentsSettings; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.UUID; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class CreateAgentIT { + + private static String PROJECT_ID = System.getenv().get("GOOGLE_CLOUD_PROJECT"); + private static String agentPath = ""; + private ByteArrayOutputStream stdOut; + private static PrintStream originalOut; + + @Before + public void setUp() throws IOException { + originalOut = System.out; + stdOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdOut)); + } + + @After + public void tearDown() throws IOException, InterruptedException { + System.setOut(originalOut); + String apiEndpoint = "global-dialogflow.googleapis.com:443"; + + AgentsSettings agentsSettings = AgentsSettings.newBuilder().setEndpoint(apiEndpoint).build(); + try (AgentsClient client = AgentsClient.create(agentsSettings)) { + client.deleteAgent(CreateAgentIT.agentPath); + + // Small delay to prevent reaching quota limit of requests per minute + Thread.sleep(250); + } + } + + @Test + public void testCreateAgent() throws IOException { + String fakeAgent = String.format("fake_agent_%s", UUID.randomUUID().toString()); + + CreateAgentIT.agentPath = CreateAgent.createAgent(PROJECT_ID, fakeAgent).getName(); + + assertThat(stdOut.toString()).contains(fakeAgent); + } +} diff --git a/dialogflow-cx/src/test/java/dialogflow/cx/CreateFlowIT.java b/dialogflow-cx/src/test/java/dialogflow/cx/CreateFlowIT.java new file mode 100644 index 00000000000..7bbe9de68d6 --- /dev/null +++ b/dialogflow-cx/src/test/java/dialogflow/cx/CreateFlowIT.java @@ -0,0 +1,108 @@ +/* + * 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 dialogflow.cx; + +import static org.junit.Assert.assertEquals; + +import com.google.cloud.dialogflow.cx.v3beta1.Flow; +import com.google.cloud.dialogflow.cx.v3beta1.FlowsClient; +import com.google.cloud.dialogflow.cx.v3beta1.FlowsSettings; +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import java.util.UUID; +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Integration (system) tests for {@link CreateFlow}. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class CreateFlowIT { + + private static String DISPLAY_NAME = "flow-" + UUID.randomUUID().toString(); + private static String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static String LOCATION_GLOBAL = "global"; + private static String LOCATION_REGIONAL = "us-central1"; + private static String AGENT_ID_GLOBAL = + System.getenv() + .getOrDefault("DIALOGFLOW_CX_AGENT_ID_GLOBAL", "b8d0e85d-0741-4e6d-a66a-3671184b7b93"); + private static String AGENT_ID_REGIONAL = + System.getenv() + .getOrDefault("DIALOGFLOW_CX_AGENT_ID_REGIONAL", "1ea2bf10-d5ef-4442-b93f-a917d1991014"); + private static Map EVENT_TO_FULFILLMENT_MESSAGES = + ImmutableMap.of("event-1", "message-1", "event-2", "message-2"); + + private static String newFlowNameGlobal; + private static String newFlowNameRegional; + + @AfterClass + public static void tearDown() throws Exception { + // Delete the newly created Flow in the global location. + if (newFlowNameGlobal != null) { + try (FlowsClient flowsClient = FlowsClient.create()) { + flowsClient.deleteFlow(newFlowNameGlobal); + } + } + + // Delete the newly created Flow in the regional location. + if (newFlowNameRegional != null) { + FlowsSettings flowsSettings = + FlowsSettings.newBuilder() + .setEndpoint(LOCATION_REGIONAL + "-dialogflow.googleapis.com:443") + .build(); + try (FlowsClient flowsClient = FlowsClient.create(flowsSettings)) { + flowsClient.deleteFlow(newFlowNameRegional); + } + + // Small delay to prevent reaching quota limit of requests per minute + Thread.sleep(250); + } + } + + @Test + public void testCreateFlowGlobal() throws Exception { + Flow result = + CreateFlow.createFlow( + DISPLAY_NAME, + PROJECT_ID, + LOCATION_GLOBAL, + AGENT_ID_GLOBAL, + EVENT_TO_FULFILLMENT_MESSAGES); + newFlowNameGlobal = result.getName(); + + assertEquals(result.getDisplayName(), DISPLAY_NAME); + // Number of added event handlers + 2 default event handlers. + assertEquals(result.getEventHandlersCount(), EVENT_TO_FULFILLMENT_MESSAGES.size() + 2); + } + + @Test + public void testCreateFlowRegional() throws Exception { + Flow result = + CreateFlow.createFlow( + DISPLAY_NAME, + PROJECT_ID, + LOCATION_REGIONAL, + AGENT_ID_REGIONAL, + EVENT_TO_FULFILLMENT_MESSAGES); + newFlowNameRegional = result.getName(); + + assertEquals(result.getDisplayName(), DISPLAY_NAME); + // Number of added event handlers + 2 default event handlers. + assertEquals(result.getEventHandlersCount(), EVENT_TO_FULFILLMENT_MESSAGES.size() + 2); + } +} diff --git a/dialogflow-cx/src/test/java/dialogflow/cx/CreateIntentIT.java b/dialogflow-cx/src/test/java/dialogflow/cx/CreateIntentIT.java new file mode 100644 index 00000000000..6f317122f69 --- /dev/null +++ b/dialogflow-cx/src/test/java/dialogflow/cx/CreateIntentIT.java @@ -0,0 +1,107 @@ +/* + * 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 dialogflow.cx; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.google.cloud.dialogflow.cx.v3beta1.Intent; +import com.google.cloud.dialogflow.cx.v3beta1.Intent.TrainingPhrase; +import com.google.cloud.dialogflow.cx.v3beta1.IntentsClient; +import com.google.cloud.dialogflow.cx.v3beta1.IntentsSettings; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Integration (system) tests for {@link CreateIntent}. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class CreateIntentIT { + + private static String DISPLAY_NAME = "intent-" + UUID.randomUUID().toString(); + private static String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static String LOCATION_GLOBAL = "global"; + private static String LOCATION_REGIONAL = "us-central1"; + private static String AGENT_ID_GLOBAL = + System.getenv() + .getOrDefault("DIALOGFLOW_CX_AGENT_ID_GLOBAL", "b8d0e85d-0741-4e6d-a66a-3671184b7b93"); + private static String AGENT_ID_REGIONAL = + System.getenv() + .getOrDefault("DIALOGFLOW_CX_AGENT_ID_REGIONAL", "1ea2bf10-d5ef-4442-b93f-a917d1991014"); + private static List TRAINING_PHRASES_PARTS = Arrays.asList("red", "blue", "green"); + + private static String newIntentNameGlobal; + private static String newIntentNameRegional; + + @AfterClass + public static void tearDown() throws Exception { + // Delete the newly created Intent in the global location. + if (newIntentNameGlobal != null) { + try (IntentsClient intentsClient = IntentsClient.create()) { + intentsClient.deleteIntent(newIntentNameGlobal); + } + } + + // Delete the newly created Intent in the regional location. + if (newIntentNameRegional != null) { + IntentsSettings intentsSettings = + IntentsSettings.newBuilder() + .setEndpoint(LOCATION_REGIONAL + "-dialogflow.googleapis.com:443") + .build(); + try (IntentsClient intentsClient = IntentsClient.create(intentsSettings)) { + intentsClient.deleteIntent(newIntentNameRegional); + } + } + + // Small delay to prevent reaching quota limit of requests per minute + Thread.sleep(250); + } + + @Test + public void testCreateIntentGlobal() throws Exception { + Intent result = + CreateIntent.createIntent( + DISPLAY_NAME, PROJECT_ID, LOCATION_GLOBAL, AGENT_ID_GLOBAL, TRAINING_PHRASES_PARTS); + newIntentNameGlobal = result.getName(); + + assertEquals(result.getTrainingPhrasesCount(), TRAINING_PHRASES_PARTS.size()); + for (TrainingPhrase trainingPhrase : result.getTrainingPhrasesList()) { + assertEquals(trainingPhrase.getPartsCount(), 1); + String partText = trainingPhrase.getParts(0).getText(); + assertTrue(partText.equals("red") || partText.equals("blue") || partText.equals("green")); + } + } + + @Test + public void testCreateIntentRegional() throws Exception { + Intent result = + CreateIntent.createIntent( + DISPLAY_NAME, PROJECT_ID, LOCATION_REGIONAL, AGENT_ID_REGIONAL, TRAINING_PHRASES_PARTS); + newIntentNameRegional = result.getName(); + + assertEquals(result.getTrainingPhrasesCount(), TRAINING_PHRASES_PARTS.size()); + for (TrainingPhrase trainingPhrase : result.getTrainingPhrasesList()) { + assertEquals(trainingPhrase.getPartsCount(), 1); + String partText = trainingPhrase.getParts(0).getText(); + assertTrue(partText.equals("red") || partText.equals("blue") || partText.equals("green")); + } + } +} diff --git a/dialogflow-cx/src/test/java/dialogflow/cx/CreatePageIT.java b/dialogflow-cx/src/test/java/dialogflow/cx/CreatePageIT.java new file mode 100644 index 00000000000..b392be98cba --- /dev/null +++ b/dialogflow-cx/src/test/java/dialogflow/cx/CreatePageIT.java @@ -0,0 +1,108 @@ +/* + * 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 dialogflow.cx; + +import static org.junit.Assert.assertEquals; + +import com.google.cloud.dialogflow.cx.v3beta1.Page; +import com.google.cloud.dialogflow.cx.v3beta1.PagesClient; +import com.google.cloud.dialogflow.cx.v3beta1.PagesSettings; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Integration (system) tests for {@link CreatePage}. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class CreatePageIT { + + private static String DISPLAY_NAME = "page-" + UUID.randomUUID().toString(); + private static String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static String LOCATION_GLOBAL = "global"; + private static String LOCATION_REGIONAL = "us-central1"; + private static String AGENT_ID_GLOBAL = + System.getenv() + .getOrDefault("DIALOGFLOW_CX_AGENT_ID_GLOBAL", "b8d0e85d-0741-4e6d-a66a-3671184b7b93"); + private static String AGENT_ID_REGIONAL = + System.getenv() + .getOrDefault("DIALOGFLOW_CX_AGENT_ID_REGIONAL", "1ea2bf10-d5ef-4442-b93f-a917d1991014"); + private static String DEFAULT_START_FLOW_ID = "00000000-0000-0000-0000-000000000000"; + private static List ENTRY_TEXTS = Arrays.asList("Hi", "Hello", "How can I help you?"); + + private static String newPageNameGlobal; + private static String newPageNameRegional; + + @AfterClass + public static void tearDown() throws Exception { + // Delete the newly created Page in the global location. + if (newPageNameGlobal != null) { + try (PagesClient pagesClient = PagesClient.create()) { + pagesClient.deletePage(newPageNameGlobal); + } + + // Small delay to prevent reaching quota limit of requests per minute + Thread.sleep(250); + } + + // Delete the newly created Page in the regional location. + if (newPageNameRegional != null) { + PagesSettings pagesSettings = + PagesSettings.newBuilder() + .setEndpoint(LOCATION_REGIONAL + "-dialogflow.googleapis.com:443") + .build(); + try (PagesClient pagesClient = PagesClient.create(pagesSettings)) { + pagesClient.deletePage(newPageNameRegional); + } + } + } + + @Test + public void testCreatePageGlobal() throws Exception { + Page result = + CreatePage.createPage( + DISPLAY_NAME, + PROJECT_ID, + LOCATION_GLOBAL, + AGENT_ID_GLOBAL, + DEFAULT_START_FLOW_ID, + ENTRY_TEXTS); + newPageNameGlobal = result.getName(); + + assertEquals(result.getDisplayName(), DISPLAY_NAME); + assertEquals(result.getEntryFulfillment().getMessagesCount(), ENTRY_TEXTS.size()); + } + + @Test + public void testCreatePageRegional() throws Exception { + Page result = + CreatePage.createPage( + DISPLAY_NAME, + PROJECT_ID, + LOCATION_REGIONAL, + AGENT_ID_REGIONAL, + DEFAULT_START_FLOW_ID, + ENTRY_TEXTS); + newPageNameRegional = result.getName(); + + assertEquals(result.getDisplayName(), DISPLAY_NAME); + assertEquals(result.getEntryFulfillment().getMessagesCount(), ENTRY_TEXTS.size()); + } +} diff --git a/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentAudioInputTest.java b/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentAudioInputTest.java new file mode 100644 index 00000000000..ffe3619149d --- /dev/null +++ b/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentAudioInputTest.java @@ -0,0 +1,72 @@ +/* + * 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 dialogflow.cx; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.UUID; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** Unit test for {@link DetectIntentIntentAudioInput}. */ +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class DetectIntentAudioInputTest { + + private static String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static String LOCATION = "global"; + private static String AGENT_ID = + System.getenv() + .getOrDefault("DIALOGFLOW_CX_AGENT_ID_GLOBAL", "b8d0e85d-0741-4e6d-a66a-3671184b7b93"); + private static String AUDIO_FILE_NAME = "resources/book_a_room.wav"; + private static int SAMPLE_RATE_HERTZ = 16000; + private static String SESSION_ID = UUID.randomUUID().toString(); + private static String LANGUAGE_CODE = "en"; + + private ByteArrayOutputStream stdOut; + + @Before + public void setUp() throws IOException { + + stdOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdOut)); + } + + @After + public void tearDown() throws IOException { + stdOut = null; + System.setOut(null); + } + + @Test + public void testDetectIntentAudioInput() throws Exception { + + DetectIntentAudioInput.detectIntent( + PROJECT_ID, + LOCATION, + AGENT_ID, + AUDIO_FILE_NAME, + SAMPLE_RATE_HERTZ, + SESSION_ID, + LANGUAGE_CODE); + System.out.println(stdOut.toString()); + assertThat(stdOut.toString()).contains("Detected Intent:"); + } +} diff --git a/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentDisableWebhookTest.java b/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentDisableWebhookTest.java new file mode 100644 index 00000000000..3564ed1cc2f --- /dev/null +++ b/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentDisableWebhookTest.java @@ -0,0 +1,74 @@ +/* + * 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 dialogflow.cx; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.dialogflow.cx.v3.QueryResult; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** Unit test for {@link DetectIntentDisableWebhook}. */ +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class DetectIntentDisableWebhookTest { + + private static String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static String LOCATION = "global"; + private static String AGENT_ID = + System.getenv() + .getOrDefault("DIALOGFLOW_CX_AGENT_ID_GLOBAL", "b8d0e85d-0741-4e6d-a66a-3671184b7b93"); + private static String SESSION_ID = UUID.randomUUID().toString(); + private static String LANGUAGE_CODE = "en-US"; + private static List TEXTS = Arrays.asList("hello", "unhappy"); + + private ByteArrayOutputStream stdOut; + + @Before + public void setUp() throws IOException { + + stdOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdOut)); + } + + @After + public void tearDown() throws IOException { + stdOut = null; + System.setOut(null); + } + + @Test + public void testDetectIntentDisableWebhook() throws Exception { + Map queryResults = + DetectIntentDisableWebhook.detectIntent( + PROJECT_ID, LOCATION, AGENT_ID, SESSION_ID, TEXTS, LANGUAGE_CODE); + + for (int i = 0; i < TEXTS.size(); i++) { + String text = TEXTS.get(i); + float score = queryResults.get(text).getSentimentAnalysisResult().getScore(); + System.out.println(stdOut.toString()); + assertThat(stdOut.toString()).contains("disable_webhook"); + } + } +} diff --git a/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentEventInputTest.java b/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentEventInputTest.java new file mode 100644 index 00000000000..f6362519411 --- /dev/null +++ b/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentEventInputTest.java @@ -0,0 +1,66 @@ +/* + * 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 dialogflow.cx; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.UUID; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** Unit test for {@link DetectIntentEventInput}. */ +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class DetectIntentEventInputTest { + + private static String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static String LOCATION = "global"; + private static String AGENT_ID = + System.getenv() + .getOrDefault("DIALOGFLOW_CX_AGENT_ID_GLOBAL", "b8d0e85d-0741-4e6d-a66a-3671184b7b93"); + private static String EVENT = "sys.no-match-default"; + private static String SESSION_ID = UUID.randomUUID().toString(); + private static String LANGUAGE_CODE = "en-US"; + + private ByteArrayOutputStream stdOut; + + @Before + public void setUp() throws IOException { + + stdOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdOut)); + } + + @After + public void tearDown() throws IOException { + stdOut = null; + System.setOut(null); + } + + @Test + public void testDetectIntentEventInput() throws Exception { + String triggeringEvent = "sys.no-match-default"; + + DetectIntentEventInput.detectIntent( + PROJECT_ID, LOCATION, AGENT_ID, SESSION_ID, EVENT, LANGUAGE_CODE); + System.out.println(stdOut.toString()); + assertThat(stdOut.toString()).contains(triggeringEvent); + } +} diff --git a/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentIT.java b/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentIT.java new file mode 100644 index 00000000000..66d356a9b2d --- /dev/null +++ b/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentIT.java @@ -0,0 +1,78 @@ +/* + * 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 dialogflow.cx; + +import static org.junit.Assert.assertEquals; + +import com.google.cloud.dialogflow.cx.v3beta1.QueryResult; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Integration (system) tests for {@link DetectIntentText}. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class DetectIntentIT { + + private static String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static String LOCATION_GLOBAL = "global"; + private static String LOCATION_REGIONAL = "us-central1"; + private static String AGENT_ID_GLOBAL = + System.getenv() + .getOrDefault("DIALOGFLOW_CX_AGENT_ID_GLOBAL", "b8d0e85d-0741-4e6d-a66a-3671184b7b93"); + private static String AGENT_ID_REGIONAL = + System.getenv() + .getOrDefault("DIALOGFLOW_CX_AGENT_ID_REGIONAL", "1ea2bf10-d5ef-4442-b93f-a917d1991014"); + private static String SESSION_ID = UUID.randomUUID().toString(); + private static String LANGUAGE_CODE = "en-US"; + private static List TEXTS = Arrays.asList("hello", "book a meeting room"); + + @After + public void tearDown() throws InterruptedException { + // Small delay to prevent reaching quota limit of requests per minute + Thread.sleep(250); + } + + @Test + public void testDetectIntentGlobal() throws Exception { + Map queryResults = + DetectIntent.detectIntent( + PROJECT_ID, LOCATION_GLOBAL, AGENT_ID_GLOBAL, SESSION_ID, TEXTS, LANGUAGE_CODE); + assertEquals(queryResults.size(), TEXTS.size()); + for (int i = 0; i < TEXTS.size(); i++) { + String text = TEXTS.get(i); + assertEquals(queryResults.get(text).getText(), text); + } + } + + @Test + public void testDetectIntentRegional() throws Exception { + Map queryResults = + DetectIntent.detectIntent( + PROJECT_ID, LOCATION_REGIONAL, AGENT_ID_REGIONAL, SESSION_ID, TEXTS, LANGUAGE_CODE); + assertEquals(queryResults.size(), TEXTS.size()); + for (int i = 0; i < TEXTS.size(); i++) { + String text = TEXTS.get(i); + assertEquals(queryResults.get(text).getText(), text); + } + } +} diff --git a/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentIntentInputTest.java b/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentIntentInputTest.java new file mode 100644 index 00000000000..51dc3a560f8 --- /dev/null +++ b/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentIntentInputTest.java @@ -0,0 +1,75 @@ +/* + * 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 dialogflow.cx; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.UUID; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** Unit test for {@link DetectIntentIntentInput}. */ +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class DetectIntentIntentInputTest { + + private static String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static String LOCATION = "global"; + private static String AGENT_ID = + System.getenv() + .getOrDefault("DIALOGFLOW_CX_AGENT_ID_GLOBAL", "b8d0e85d-0741-4e6d-a66a-3671184b7b93"); + private static String INTENT_ID = "00000000-0000-0000-0000-000000000000"; + private static String SESSION_ID = UUID.randomUUID().toString(); + private static String LANGUAGE_CODE = "en-US"; + private static String INTENT = + "projects/" + + PROJECT_ID + + "/locations/" + + LOCATION + + "/agents/" + + AGENT_ID + + "/intents/" + + INTENT_ID; + + private ByteArrayOutputStream stdOut; + + @Before + public void setUp() throws IOException { + + stdOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdOut)); + } + + @After + public void tearDown() throws IOException { + stdOut = null; + System.setOut(null); + } + + @Test + public void testDetectIntentIntentInput() throws Exception { + String intentName = "Default Welcome Intent"; + + DetectIntentIntentInput.detectIntent( + PROJECT_ID, LOCATION, AGENT_ID, SESSION_ID, INTENT, LANGUAGE_CODE); + System.out.println(stdOut.toString()); + assertThat(stdOut.toString()).contains(intentName); + } +} diff --git a/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentSentimentAnalysisTest.java b/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentSentimentAnalysisTest.java new file mode 100644 index 00000000000..2194a9e70b8 --- /dev/null +++ b/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentSentimentAnalysisTest.java @@ -0,0 +1,56 @@ +/* + * 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 dialogflow.cx; + +import static org.junit.Assert.assertTrue; + +import com.google.cloud.dialogflow.cx.v3.QueryResult; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.junit.Test; + +/** Unit test for {@link DetectIntentSentimentAnalysis}. */ +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class DetectIntentSentimentAnalysisTest { + + private static String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static String LOCATION = "global"; + private static String AGENT_ID = + System.getenv() + .getOrDefault("DIALOGFLOW_CX_AGENT_ID_GLOBAL", "b8d0e85d-0741-4e6d-a66a-3671184b7b93"); + private static String SESSION_ID = UUID.randomUUID().toString(); + private static String LANGUAGE_CODE = "en-US"; + private static List TEXTS = Arrays.asList("hello", "unhappy"); + + @Test + public void testDetectIntentSentimentAnalysis() throws Exception { + int min = -1; + int max = 1; + + Map queryResults = + DetectIntentSentimentAnalysis.detectIntent( + PROJECT_ID, LOCATION, AGENT_ID, SESSION_ID, TEXTS, LANGUAGE_CODE); + + for (int i = 0; i < TEXTS.size(); i++) { + String text = TEXTS.get(i); + float score = queryResults.get(text).getSentimentAnalysisResult().getScore(); + assertTrue(min <= score && score <= max); + } + } +} diff --git a/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentStreamIT.java b/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentStreamIT.java new file mode 100644 index 00000000000..b941420f055 --- /dev/null +++ b/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentStreamIT.java @@ -0,0 +1,87 @@ +/* + * 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 dialogflow.cx; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.UUID; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Integration (system) tests for {@link DetectIntentStream}. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class DetectIntentStreamIT { + + private static String AUDIO_FILE_PATH = "resources/book_a_room.wav"; + private static String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static String LOCATION_GLOBAL = "global"; + private static String LOCATION_REGIONAL = "us-central1"; + private static String AGENT_ID_GLOBAL = + System.getenv() + .getOrDefault("DIALOGFLOW_CX_AGENT_ID_GLOBAL", "b8d0e85d-0741-4e6d-a66a-3671184b7b93"); + private static String AGENT_ID_REGIONAL = + System.getenv() + .getOrDefault("DIALOGFLOW_CX_AGENT_ID_REGIONAL", "1ea2bf10-d5ef-4442-b93f-a917d1991014"); + private static String SESSION_ID = UUID.randomUUID().toString(); + private ByteArrayOutputStream bout; + private PrintStream original; + + @Before + public void setUp() { + original = System.out; + bout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(bout)); + } + + @After + public void tearDown() throws InterruptedException { + System.setOut(original); + bout.reset(); + + // Small delay to prevent reaching quota limit of requests per minute + Thread.sleep(250); + } + + @Test + public void testDetectIntentStreamGlobal() throws IOException { + DetectIntentStream.detectIntentStream( + PROJECT_ID, LOCATION_GLOBAL, AGENT_ID_GLOBAL, SESSION_ID, AUDIO_FILE_PATH); + + String output = bout.toString(); + + assertThat(output).contains("Detected Intent"); + assertThat(output).contains("book"); + } + + @Test + public void testDetectIntentStreamRegional() throws IOException { + DetectIntentStream.detectIntentStream( + PROJECT_ID, LOCATION_REGIONAL, AGENT_ID_REGIONAL, SESSION_ID, AUDIO_FILE_PATH); + + String output = bout.toString(); + + assertThat(output).contains("Detected Intent"); + assertThat(output).contains("book"); + } +} diff --git a/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentStreamingPartialResponseTest.java b/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentStreamingPartialResponseTest.java new file mode 100644 index 00000000000..7f6f24a2ee4 --- /dev/null +++ b/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentStreamingPartialResponseTest.java @@ -0,0 +1,68 @@ +/* + * 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 dialogflow.cx; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.UUID; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Integration (system) tests for {@link DetectIntentStream}. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class DetectIntentStreamingPartialResponseTest { + + private static String AUDIO_FILE_PATH = "resources/book_a_room.wav"; + private static String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static String LOCATION = "global"; + private static String AGENT_ID = + System.getenv() + .getOrDefault("DIALOGFLOW_CX_AGENT_ID_GLOBAL", "b8d0e85d-0741-4e6d-a66a-3671184b7b93"); + private static String SESSION_ID = UUID.randomUUID().toString(); + private ByteArrayOutputStream bout; + private PrintStream original; + + @Before + public void setUp() { + original = System.out; + bout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(bout)); + } + + @After + public void tearDown() { + System.setOut(original); + bout.reset(); + } + + @Test + public void testDetectIntentStreamingPartialResponse() throws IOException { + DetectIntentStreamingPartialResponse.detectIntentStreamingPartialResponse( + PROJECT_ID, LOCATION, AGENT_ID, SESSION_ID, AUDIO_FILE_PATH); + + String output = bout.toString(); + + assertThat(output).contains("enable_partial_response"); + } +} diff --git a/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentSynthesizeTextToSpeechOutputTest.java b/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentSynthesizeTextToSpeechOutputTest.java new file mode 100644 index 00000000000..20d84e37a1e --- /dev/null +++ b/dialogflow-cx/src/test/java/dialogflow/cx/DetectIntentSynthesizeTextToSpeechOutputTest.java @@ -0,0 +1,72 @@ +/* + * 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 dialogflow.cx; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.UUID; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** Unit test for {@link DetectIntentSynthesizeTtSOutput}. */ +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class DetectIntentSynthesizeTextToSpeechOutputTest { + + private static String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static String LOCATION = "global"; + private static String AGENT_ID = + System.getenv() + .getOrDefault("DIALOGFLOW_CX_AGENT_ID_GLOBAL", "b8d0e85d-0741-4e6d-a66a-3671184b7b93"); + private static String AUDIO_FILE_NAME = "resources/book_a_room.wav"; + private static int SAMPLE_RATE_HERTZ = 16000; + private static String SESSION_ID = UUID.randomUUID().toString(); + private static String LANGUAGE_CODE = "en"; + + private ByteArrayOutputStream stdOut; + + @Before + public void setUp() throws IOException { + + stdOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdOut)); + } + + @After + public void tearDown() throws IOException { + stdOut = null; + System.setOut(null); + } + + @Test + public void testDetectIntentSynthesizeTextToSpeechOutput() throws Exception { + + DetectIntentSynthesizeTextToSpeechOutput.detectIntent( + PROJECT_ID, + LOCATION, + AGENT_ID, + AUDIO_FILE_NAME, + SAMPLE_RATE_HERTZ, + SESSION_ID, + LANGUAGE_CODE); + System.out.println(stdOut.toString()); + assertThat(stdOut.toString()).contains("speaking_rate"); + } +} diff --git a/dialogflow-cx/src/test/java/dialogflow/cx/ExportAgentIT.java b/dialogflow-cx/src/test/java/dialogflow/cx/ExportAgentIT.java new file mode 100644 index 00000000000..e499fe726d7 --- /dev/null +++ b/dialogflow-cx/src/test/java/dialogflow/cx/ExportAgentIT.java @@ -0,0 +1,96 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dialogflow.cx; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.dialogflow.cx.v3.Agent; +import com.google.cloud.dialogflow.cx.v3.Agent.Builder; +import com.google.cloud.dialogflow.cx.v3.AgentsClient; +import com.google.cloud.dialogflow.cx.v3.AgentsSettings; +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.Test; + +public class ExportAgentIT { + + private static String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static String parent = ""; + private static String agentPath = ""; + private static String agentID = ""; + + private ByteArrayOutputStream stdOut; + + @BeforeClass + public static void beforeAll() { + assertThat(PROJECT_ID).isNotNull(); + } + + @Before + public void setUp() throws IOException { + stdOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdOut)); + + Builder build = Agent.newBuilder(); + build.setDefaultLanguageCode("en"); + build.setDisplayName("temp_agent_" + UUID.randomUUID().toString()); + build.setTimeZone("America/Los_Angeles"); + + Agent agent = build.build(); + + String apiEndpoint = "global-dialogflow.googleapis.com:443"; + String parentPath = "projects/" + PROJECT_ID + "/locations/global"; + + AgentsSettings agentsSettings = AgentsSettings.newBuilder().setEndpoint(apiEndpoint).build(); + AgentsClient client = AgentsClient.create(agentsSettings); + + parent = client.createAgent(parentPath, agent).getName(); + ExportAgentIT.agentPath = parent; + ExportAgentIT.agentID = parent.split("/")[5]; + client.close(); + } + + @After + public void tearDown() throws IOException, InterruptedException { + stdOut = null; + System.setOut(null); + String apiEndpoint = "global-dialogflow.googleapis.com:443"; + + AgentsSettings agentsSettings = AgentsSettings.newBuilder().setEndpoint(apiEndpoint).build(); + AgentsClient client = AgentsClient.create(agentsSettings); + + client.deleteAgent(ExportAgentIT.agentPath); + client.close(); + + // Small delay to prevent reaching quota limit of requests per minute + Thread.sleep(250); + } + + @Test + public void testUpdateExportAgent() throws IOException, InterruptedException, ExecutionException { + + ExportAgent.exportAgent(PROJECT_ID, ExportAgentIT.agentID, "global"); + + assertThat(stdOut.toString()).contains(ExportAgentIT.agentID); + } +} diff --git a/dialogflow-cx/src/test/java/dialogflow/cx/ListTestCaseResultsIT.java b/dialogflow-cx/src/test/java/dialogflow/cx/ListTestCaseResultsIT.java new file mode 100644 index 00000000000..2214356f1c9 --- /dev/null +++ b/dialogflow-cx/src/test/java/dialogflow/cx/ListTestCaseResultsIT.java @@ -0,0 +1,58 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dialogflow.cx; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class ListTestCaseResultsIT { + + private static String PROJECT_ID = System.getenv().get("GOOGLE_CLOUD_PROJECT"); + private static String agentId = "1499b8e1-ab7d-43fd-9b08-30ee57194fc1"; + private static String testId = "694a5447-6c40-4752-944e-e3e70580b273"; + private static String location = "global"; + + private ByteArrayOutputStream stdOut; + private static PrintStream originalOut; + + @Before + public void setUp() throws IOException { + originalOut = System.out; + stdOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdOut)); + } + + @After + public void tearDown() throws IOException, InterruptedException { + System.setOut(originalOut); + + // Small delay to prevent reaching quota limit of requests per minute + Thread.sleep(250); + } + + @Test + public void testListTestCaseResults() throws IOException { + ListTestCaseResults.listTestCaseResults(PROJECT_ID, agentId, testId, location); + assertThat(stdOut.toString()).contains(testId); + } +} diff --git a/dialogflow-cx/src/test/java/dialogflow/cx/PageManagementIT.java b/dialogflow-cx/src/test/java/dialogflow/cx/PageManagementIT.java new file mode 100644 index 00000000000..47c100107d4 --- /dev/null +++ b/dialogflow-cx/src/test/java/dialogflow/cx/PageManagementIT.java @@ -0,0 +1,117 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dialogflow.cx; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.dialogflow.cx.v3.Agent; +import com.google.cloud.dialogflow.cx.v3.Agent.Builder; +import com.google.cloud.dialogflow.cx.v3.AgentsClient; +import com.google.cloud.dialogflow.cx.v3.AgentsSettings; +import com.google.cloud.dialogflow.cx.v3.Page; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.UUID; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +/** Integration (system) tests for {@link PageManagment}. */ +public class PageManagementIT { + + private static String PROJECT_ID = System.getenv().get("GOOGLE_CLOUD_PROJECT"); + private static String flowID = "00000000-0000-0000-0000-000000000000"; + private static String location = "global"; + private static String displayName = "temp_page_" + UUID.randomUUID().toString(); + private static String parent; + private static String agentID; + private static String pageID; + private static ByteArrayOutputStream stdOut; + + @BeforeClass + public static void setUp() throws IOException { + stdOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdOut)); + + String apiEndpoint = "global-dialogflow.googleapis.com:443"; + + AgentsSettings agentsSettings = AgentsSettings.newBuilder().setEndpoint(apiEndpoint).build(); + try (AgentsClient client = AgentsClient.create(agentsSettings)) { + + Builder build = Agent.newBuilder(); + build.setDefaultLanguageCode("en"); + build.setDisplayName("temp_agent_" + UUID.randomUUID().toString()); + build.setTimeZone("America/Los_Angeles"); + + Agent agent = build.build(); + String parentPath = "projects/" + PROJECT_ID + "/locations/global"; + + parent = client.createAgent(parentPath, agent).getName(); + + agentID = parent.split("/")[5]; + } + } + + @AfterClass + public static void tearDown() throws IOException, InterruptedException { + String apiEndpoint = "global-dialogflow.googleapis.com:443"; + String parentPath = "projects/" + PROJECT_ID + "/locations/global"; + + AgentsSettings agentsSettings = AgentsSettings.newBuilder().setEndpoint(apiEndpoint).build(); + try (AgentsClient client = AgentsClient.create(agentsSettings)) { + client.deleteAgent(parent); + + // Small delay to prevent reaching quota limit of requests per minute + Thread.sleep(250); + } + } + + @Test + public void testCreatePage() throws IOException { + try { + Page p = CreateSimplePage.createPage(PROJECT_ID, agentID, flowID, location, displayName); + pageID = p.getName().split("/")[9]; + assertThat(p.getDisplayName()).isEqualTo(displayName); + } catch (Exception e) { + assertThat(e).isEqualTo(""); + } + } + + @Test + public void testListPages() throws IOException { + String name = "temp_page_" + UUID.randomUUID().toString(); + // Page p + try { + CreateSimplePage.createPage(PROJECT_ID, agentID, flowID, location, name); + ListPages.listPages(PROJECT_ID, agentID, flowID, location); + assertThat(stdOut.toString()).contains(name); + } catch (Exception e) { + assertThat(e).isEqualTo(""); + } + } + + @Test + public void testDeletePage() throws IOException { + try { + DeletePage.deletePage(PROJECT_ID, agentID, flowID, pageID, location); + assertThat(1).isEqualTo(1); + } catch (Exception e) { + assertThat(e).isEqualTo(""); + } + } +} diff --git a/dialogflow-cx/src/test/java/dialogflow/cx/UpdateIntentTest.java b/dialogflow-cx/src/test/java/dialogflow/cx/UpdateIntentTest.java new file mode 100644 index 00000000000..635b16b781b --- /dev/null +++ b/dialogflow-cx/src/test/java/dialogflow/cx/UpdateIntentTest.java @@ -0,0 +1,106 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dialogflow.cx; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.dialogflow.cx.v3.Agent; +import com.google.cloud.dialogflow.cx.v3.Agent.Builder; +import com.google.cloud.dialogflow.cx.v3.AgentsClient; +import com.google.cloud.dialogflow.cx.v3.AgentsSettings; +import com.google.cloud.dialogflow.cx.v3.Intent; +import com.google.cloud.dialogflow.cx.v3.IntentsClient; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.UUID; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class UpdateIntentTest { + + private static String PROJECT_ID = System.getenv().get("GOOGLE_CLOUD_PROJECT"); + private static String parent = ""; + private static String intentID = ""; + private static String intentPath = ""; + private static String agentID = ""; + + private ByteArrayOutputStream stdOut; + + @Before + public void setUp() throws IOException { + + stdOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdOut)); + + String apiEndpoint = "global-dialogflow.googleapis.com:443"; + + AgentsSettings agentsSettings = AgentsSettings.newBuilder().setEndpoint(apiEndpoint).build(); + try (AgentsClient agentsClient = AgentsClient.create(agentsSettings)) { + + Builder build = Agent.newBuilder(); + + build.setDefaultLanguageCode("en"); + build.setDisplayName("temp_agent_" + UUID.randomUUID().toString()); + build.setTimeZone("America/Los_Angeles"); + + Agent agent = build.build(); + String parentPath = String.format("projects/%s/locations/global", PROJECT_ID); + + parent = agentsClient.createAgent(parentPath, agent).getName(); + + UpdateIntentTest.agentID = parent.split("/")[5]; + } + + try (IntentsClient intentsClient = IntentsClient.create()) { + com.google.cloud.dialogflow.cx.v3.Intent.Builder intent = Intent.newBuilder(); + intent.setDisplayName("temp_intent_" + UUID.randomUUID().toString()); + + UpdateIntentTest.intentPath = intentsClient.createIntent(parent, intent.build()).getName(); + UpdateIntentTest.intentID = UpdateIntentTest.intentPath.split("/")[7]; + } + } + + @After + public void tearDown() throws IOException, InterruptedException { + stdOut = null; + System.setOut(null); + + String apiEndpoint = "global-dialogflow.googleapis.com:443"; + + AgentsSettings agentsSettings = AgentsSettings.newBuilder().setEndpoint(apiEndpoint).build(); + try (AgentsClient client = AgentsClient.create(agentsSettings)) { + String parentPath = "projects/" + PROJECT_ID + "/locations/global"; + client.deleteAgent(parent); + + // Small delay to prevent reaching quota limit of requests per minute + Thread.sleep(250); + } + } + + @Test + public void testUpdateIntent() throws IOException { + + String fakeIntent = "fake_intent_" + UUID.randomUUID().toString(); + + UpdateIntent.updateIntent( + PROJECT_ID, UpdateIntentTest.agentID, UpdateIntentTest.intentID, "global", fakeIntent); + + assertThat(stdOut.toString()).contains(fakeIntent); + } +} diff --git a/dialogflow-cx/src/test/java/dialogflow/cx/WebhookConfigureSessionParametersIT.java b/dialogflow-cx/src/test/java/dialogflow/cx/WebhookConfigureSessionParametersIT.java new file mode 100644 index 00000000000..9c1d2a71929 --- /dev/null +++ b/dialogflow-cx/src/test/java/dialogflow/cx/WebhookConfigureSessionParametersIT.java @@ -0,0 +1,87 @@ +/* + * 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 dialogflow.cx; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + +import com.google.cloud.dialogflow.cx.v3beta1.WebhookRequest; +import com.google.cloud.dialogflow.cx.v3beta1.WebhookRequest.FulfillmentInfo; +import com.google.cloud.functions.HttpRequest; +import com.google.cloud.functions.HttpResponse; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class WebhookConfigureSessionParametersIT { + + @Mock HttpRequest request; + @Mock HttpResponse response; + + BufferedReader jsonReader; + StringReader stringReader; + BufferedWriter writerOut; + StringWriter responseOut; + + @Before + public void beforeTest() throws IOException { + MockitoAnnotations.initMocks(this); + + stringReader = new StringReader("{'fulfillmentInfo': {'tag': 'configure-session-parameter'}}"); + jsonReader = new BufferedReader(stringReader); + + responseOut = new StringWriter(); + writerOut = new BufferedWriter(responseOut); + + when(request.getReader()).thenReturn(jsonReader); + when(response.getWriter()).thenReturn(writerOut); + } + + @Test + public void helloHttp_bodyParamsPost() throws IOException, Exception { + FulfillmentInfo fulfillmentInfo = + FulfillmentInfo.newBuilder().setTag("configure-session-parameters").build(); + + WebhookRequest webhookRequest = + WebhookRequest.newBuilder().setFulfillmentInfo(fulfillmentInfo).build(); + + new WebhookConfigureSessionParameters().service(request, response); + writerOut.flush(); + + JsonObject webhookResponse = new JsonObject(); + JsonObject parameterObject = new JsonObject(); + JsonObject orderParameter = new JsonObject(); + orderParameter.addProperty("order_number", "12345"); + parameterObject.add("parameters", orderParameter); + webhookResponse.add("session_info", parameterObject); + + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + String expectedResponse = gson.toJson(webhookResponse); + + assertThat(responseOut.toString()).isEqualTo(expectedResponse); + Thread.sleep(200); + } +} diff --git a/dialogflow-cx/src/test/java/dialogflow/cx/WebhookValidateFormParameterIT.java b/dialogflow-cx/src/test/java/dialogflow/cx/WebhookValidateFormParameterIT.java new file mode 100644 index 00000000000..4500e3f9467 --- /dev/null +++ b/dialogflow-cx/src/test/java/dialogflow/cx/WebhookValidateFormParameterIT.java @@ -0,0 +1,106 @@ +/* + * 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 dialogflow.cx; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + +import com.google.cloud.dialogflow.cx.v3beta1.WebhookRequest; +import com.google.cloud.dialogflow.cx.v3beta1.WebhookRequest.FulfillmentInfo; +import com.google.cloud.functions.HttpRequest; +import com.google.cloud.functions.HttpResponse; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class WebhookValidateFormParameterIT { + + @Mock HttpRequest request; + @Mock HttpResponse response; + + BufferedReader jsonReader; + StringReader stringReader; + BufferedWriter writerOut; + StringWriter responseOut; + + @Before + public void beforeTest() throws IOException { + MockitoAnnotations.initMocks(this); + + stringReader = new StringReader("{'fulfillmentInfo': {'tag': 'validate-form-parameter'}}"); + jsonReader = new BufferedReader(stringReader); + + responseOut = new StringWriter(); + writerOut = new BufferedWriter(responseOut); + + when(request.getReader()).thenReturn(jsonReader); + when(response.getWriter()).thenReturn(writerOut); + } + + @Test + public void helloHttp_bodyParamsPost() throws IOException, Exception { + FulfillmentInfo fulfillmentInfo = + FulfillmentInfo.newBuilder().setTag("configure-session-parameters").build(); + + WebhookRequest webhookRequest = + WebhookRequest.newBuilder().setFulfillmentInfo(fulfillmentInfo).build(); + + new WebhookValidateFormParameter().service(request, response); + writerOut.flush(); + + JsonObject sessionParameter = new JsonObject(); + sessionParameter.addProperty("order_number", "null"); + + JsonObject sessionInfo = new JsonObject(); + sessionInfo.add("parameters", sessionParameter); + + JsonObject parameterObject = new JsonObject(); + parameterObject.addProperty("display_name", "order_number"); + parameterObject.addProperty("required", "true"); + parameterObject.addProperty("state", "INVALID"); + parameterObject.addProperty("value", "123"); + + JsonArray parameterInfoList = new JsonArray(); + parameterInfoList.add(parameterObject); + + JsonObject parameterInfoObject = new JsonObject(); + parameterInfoObject.add("parameter_info", parameterInfoList); + + JsonObject formInfo = new JsonObject(); + formInfo.add("form_info", parameterInfoObject); + + JsonObject webhookResponse = new JsonObject(); + webhookResponse.add("page_info", formInfo); + webhookResponse.add("session_info", sessionInfo); + + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + String expectedResponse = gson.toJson(webhookResponse); + + assertThat(responseOut.toString()).isEqualTo(expectedResponse); + Thread.sleep(250); + } +} diff --git a/dialogflow/snippets/pom.xml b/dialogflow/snippets/pom.xml new file mode 100644 index 00000000000..0d7425bc6d1 --- /dev/null +++ b/dialogflow/snippets/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + com.example.dialogflow + dialogflow-snippets + jar + Google Dialogflow API Snippets + https://github.com/GoogleCloudPlatform/java-docs-samples/tree/main/dialogflow + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + 1.8 + 1.8 + UTF-8 + + + + + + + + com.google.cloud + libraries-bom + 26.1.3 + pom + import + + + + + + + com.google.cloud + google-cloud-dialogflow + + + + + com.google.cloud + google-cloud-core + 2.8.20 + test + tests + + + + + junit + junit + 4.13.2 + test + + + com.google.truth + truth + 1.1.3 + test + + + diff --git a/dialogflow/snippets/resources/230pm.wav b/dialogflow/snippets/resources/230pm.wav new file mode 100644 index 00000000000..7509eca784d Binary files /dev/null and b/dialogflow/snippets/resources/230pm.wav differ diff --git a/dialogflow/snippets/resources/RoomReservation.zip b/dialogflow/snippets/resources/RoomReservation.zip new file mode 100644 index 00000000000..7873fb628c8 Binary files /dev/null and b/dialogflow/snippets/resources/RoomReservation.zip differ diff --git a/dialogflow/snippets/resources/book_a_room.wav b/dialogflow/snippets/resources/book_a_room.wav new file mode 100644 index 00000000000..9124e927946 Binary files /dev/null and b/dialogflow/snippets/resources/book_a_room.wav differ diff --git a/dialogflow/snippets/resources/half_an_hour.wav b/dialogflow/snippets/resources/half_an_hour.wav new file mode 100644 index 00000000000..71010a871bb Binary files /dev/null and b/dialogflow/snippets/resources/half_an_hour.wav differ diff --git a/dialogflow/snippets/resources/mountain_view.wav b/dialogflow/snippets/resources/mountain_view.wav new file mode 100644 index 00000000000..1c5437f7cb5 Binary files /dev/null and b/dialogflow/snippets/resources/mountain_view.wav differ diff --git a/dialogflow/snippets/resources/today.wav b/dialogflow/snippets/resources/today.wav new file mode 100644 index 00000000000..d47ed78b351 Binary files /dev/null and b/dialogflow/snippets/resources/today.wav differ diff --git a/dialogflow/snippets/resources/two_people.wav b/dialogflow/snippets/resources/two_people.wav new file mode 100644 index 00000000000..5114ebbd310 Binary files /dev/null and b/dialogflow/snippets/resources/two_people.wav differ diff --git a/dialogflow/snippets/src/main/dialogflow/Example.java b/dialogflow/snippets/src/main/dialogflow/Example.java new file mode 100644 index 00000000000..24f5aaeba19 --- /dev/null +++ b/dialogflow/snippets/src/main/dialogflow/Example.java @@ -0,0 +1,72 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dialogflow; + +// [START dialogflow_webhook] + +// TODO: add GSON dependency to Pom file +// (https://mvnrepository.com/artifact/com.google.code.gson/gson/2.8.5) +// TODO: Uncomment the line bellow before running cloud function +// package com.example; + +import com.google.cloud.functions.HttpFunction; +import com.google.cloud.functions.HttpRequest; +import com.google.cloud.functions.HttpResponse; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import java.io.BufferedWriter; + +public class Example implements HttpFunction { + + public void service(HttpRequest request, HttpResponse response) throws Exception { + JsonParser parser = new JsonParser(); + Gson gson = new GsonBuilder().create(); + + JsonObject job = gson.fromJson(request.getReader(), JsonObject.class); + String str = + job.getAsJsonObject("queryResult") + .getAsJsonObject("intent") + .getAsJsonPrimitive("displayName") + .toString(); + JsonObject o = null; + String a = '"' + "Default Welcome Intent" + '"'; + String b = '"' + "get-agent-name" + '"'; + String responseText = ""; + + if (str.equals(a)) { + responseText = '"' + "Hello from a Java GCF Webhook" + '"'; + } else if (str.equals(b)) { + responseText = '"' + "My name is Flowhook" + '"'; + } else { + responseText = '"' + "Sorry I didn't get that" + '"'; + } + + o = + parser + .parse( + "{\"fulfillmentMessages\": [ { \"text\": { \"text\": [ " + + responseText + + " ] } } ] }") + .getAsJsonObject(); + + BufferedWriter writer = response.getWriter(); + writer.write(o.toString()); + } +} +// [END dialogflow_webhook] diff --git a/dialogflow/snippets/src/main/dialogflow/SetAgent.java b/dialogflow/snippets/src/main/dialogflow/SetAgent.java new file mode 100644 index 00000000000..cfa73edf26d --- /dev/null +++ b/dialogflow/snippets/src/main/dialogflow/SetAgent.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dialogflow; + +// [START dialogflow_es_create_agent] + +import com.google.cloud.dialogflow.v2.Agent; +import com.google.cloud.dialogflow.v2.Agent.Builder; +import com.google.cloud.dialogflow.v2.AgentsClient; +import com.google.cloud.dialogflow.v2.AgentsSettings; +import java.io.IOException; + +public class SetAgent { + + public static void main(String[] args) throws IOException { + String projectId = "my-project-id"; + + // The display name will set the name of your agent + String displayName = "my-display-name"; + + setAgent(projectId, displayName); + } + + public static Agent setAgent(String parent, String displayName) throws IOException { + + AgentsSettings agentsSettings = AgentsSettings.newBuilder().build(); + try (AgentsClient client = AgentsClient.create(agentsSettings)) { + // Set the details of the Agent to create + Builder build = Agent.newBuilder(); + + build.setDefaultLanguageCode("en"); + build.setDisplayName(displayName); + + Agent agent = build.build(); + + // Make API request to create agent + Agent response = client.setAgent(agent); + System.out.println(response); + return response; + } + } +} +// [END dialogflow_es_create_agent] diff --git a/dialogflow/snippets/src/main/java/com/example/dialogflow/DetectIntentAudio.java b/dialogflow/snippets/src/main/java/com/example/dialogflow/DetectIntentAudio.java new file mode 100644 index 00000000000..0d3a6955a4a --- /dev/null +++ b/dialogflow/snippets/src/main/java/com/example/dialogflow/DetectIntentAudio.java @@ -0,0 +1,95 @@ +/* + * 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.dialogflow; + +// [START dialogflow_detect_intent_audio] + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.dialogflow.v2.AudioEncoding; +import com.google.cloud.dialogflow.v2.DetectIntentRequest; +import com.google.cloud.dialogflow.v2.DetectIntentResponse; +import com.google.cloud.dialogflow.v2.InputAudioConfig; +import com.google.cloud.dialogflow.v2.QueryInput; +import com.google.cloud.dialogflow.v2.QueryResult; +import com.google.cloud.dialogflow.v2.SessionName; +import com.google.cloud.dialogflow.v2.SessionsClient; +import com.google.protobuf.ByteString; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class DetectIntentAudio { + + // DialogFlow API Detect Intent sample with audio files. + public static QueryResult detectIntentAudio( + String projectId, String audioFilePath, String sessionId, String languageCode) + throws IOException, ApiException { + // Instantiates a client + try (SessionsClient sessionsClient = SessionsClient.create()) { + // Set the session name using the sessionId (UUID) and projectID (my-project-id) + SessionName session = SessionName.of(projectId, sessionId); + System.out.println("Session Path: " + session.toString()); + + // Note: hard coding audioEncoding and sampleRateHertz for simplicity. + // Audio encoding of the audio content sent in the query request. + AudioEncoding audioEncoding = AudioEncoding.AUDIO_ENCODING_LINEAR_16; + int sampleRateHertz = 16000; + + // Instructs the speech recognizer how to process the audio content. + InputAudioConfig inputAudioConfig = + InputAudioConfig.newBuilder() + .setAudioEncoding( + audioEncoding) // audioEncoding = AudioEncoding.AUDIO_ENCODING_LINEAR_16 + .setLanguageCode(languageCode) // languageCode = "en-US" + .setSampleRateHertz(sampleRateHertz) // sampleRateHertz = 16000 + .build(); + + // Build the query with the InputAudioConfig + QueryInput queryInput = QueryInput.newBuilder().setAudioConfig(inputAudioConfig).build(); + + // Read the bytes from the audio file + byte[] inputAudio = Files.readAllBytes(Paths.get(audioFilePath)); + + // Build the DetectIntentRequest + DetectIntentRequest request = + DetectIntentRequest.newBuilder() + .setSession(session.toString()) + .setQueryInput(queryInput) + .setInputAudio(ByteString.copyFrom(inputAudio)) + .build(); + + // Performs the detect intent request + DetectIntentResponse response = sessionsClient.detectIntent(request); + + // Display the query result + QueryResult queryResult = response.getQueryResult(); + System.out.println("===================="); + System.out.format("Query Text: '%s'\n", queryResult.getQueryText()); + System.out.format( + "Detected Intent: %s (confidence: %f)\n", + queryResult.getIntent().getDisplayName(), queryResult.getIntentDetectionConfidence()); + System.out.format( + "Fulfillment Text: '%s'\n", + queryResult.getFulfillmentMessagesCount() > 0 + ? queryResult.getFulfillmentMessages(0).getText() + : "Triggered Default Fallback Intent"); + + return queryResult; + } + } +} +// [END dialogflow_detect_intent_audio] diff --git a/dialogflow/snippets/src/main/java/com/example/dialogflow/DetectIntentKnowledge.java b/dialogflow/snippets/src/main/java/com/example/dialogflow/DetectIntentKnowledge.java new file mode 100644 index 00000000000..04484eb50df --- /dev/null +++ b/dialogflow/snippets/src/main/java/com/example/dialogflow/DetectIntentKnowledge.java @@ -0,0 +1,101 @@ +/* + * 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.dialogflow; + +// [START dialogflow_detect_intent_knowledge] + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.dialogflow.v2beta1.DetectIntentRequest; +import com.google.cloud.dialogflow.v2beta1.DetectIntentResponse; +import com.google.cloud.dialogflow.v2beta1.KnowledgeAnswers; +import com.google.cloud.dialogflow.v2beta1.KnowledgeAnswers.Answer; +import com.google.cloud.dialogflow.v2beta1.QueryInput; +import com.google.cloud.dialogflow.v2beta1.QueryParameters; +import com.google.cloud.dialogflow.v2beta1.QueryResult; +import com.google.cloud.dialogflow.v2beta1.SessionName; +import com.google.cloud.dialogflow.v2beta1.SessionsClient; +import com.google.cloud.dialogflow.v2beta1.TextInput; +import com.google.common.collect.Maps; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class DetectIntentKnowledge { + + // DialogFlow API Detect Intent sample with querying knowledge connector. + public static Map detectIntentKnowledge( + String projectId, + String knowledgeBaseName, + String sessionId, + String languageCode, + List texts) + throws IOException, ApiException { + // Instantiates a client + Map allKnowledgeAnswers = Maps.newHashMap(); + try (SessionsClient sessionsClient = SessionsClient.create()) { + // Set the session name using the sessionId (UUID) and projectID (my-project-id) + SessionName session = SessionName.of(projectId, sessionId); + System.out.println("Session Path: " + session.toString()); + + // Detect intents for each text input + for (String text : texts) { + // Set the text and language code (en-US) for the query + TextInput.Builder textInput = + TextInput.newBuilder().setText(text).setLanguageCode(languageCode); + // Build the query with the TextInput + QueryInput queryInput = QueryInput.newBuilder().setText(textInput).build(); + + QueryParameters queryParameters = + QueryParameters.newBuilder().addKnowledgeBaseNames(knowledgeBaseName).build(); + + DetectIntentRequest detectIntentRequest = + DetectIntentRequest.newBuilder() + .setSession(session.toString()) + .setQueryInput(queryInput) + .setQueryParams(queryParameters) + .build(); + // Performs the detect intent request + DetectIntentResponse response = sessionsClient.detectIntent(detectIntentRequest); + + // Display the query result + QueryResult queryResult = response.getQueryResult(); + + System.out.format("Knowledge results:\n"); + System.out.format("====================\n"); + System.out.format("Query Text: '%s'\n", queryResult.getQueryText()); + System.out.format( + "Detected Intent: %s (confidence: %f)\n", + queryResult.getIntent().getDisplayName(), queryResult.getIntentDetectionConfidence()); + System.out.format( + "Fulfillment Text: '%s'\n", + queryResult.getFulfillmentMessagesCount() > 0 + ? queryResult.getFulfillmentMessages(0).getText() + : "Triggered Default Fallback Intent"); + KnowledgeAnswers knowledgeAnswers = queryResult.getKnowledgeAnswers(); + for (Answer answer : knowledgeAnswers.getAnswersList()) { + System.out.format(" - Answer: '%s'\n", answer.getAnswer()); + System.out.format(" - Confidence: '%s'\n", answer.getMatchConfidence()); + } + + KnowledgeAnswers answers = queryResult.getKnowledgeAnswers(); + allKnowledgeAnswers.put(text, answers); + } + } + return allKnowledgeAnswers; + } +} +// [END dialogflow_detect_intent_knowledge] diff --git a/dialogflow/snippets/src/main/java/com/example/dialogflow/DetectIntentStream.java b/dialogflow/snippets/src/main/java/com/example/dialogflow/DetectIntentStream.java new file mode 100644 index 00000000000..27febe126a7 --- /dev/null +++ b/dialogflow/snippets/src/main/java/com/example/dialogflow/DetectIntentStream.java @@ -0,0 +1,108 @@ +/* + * 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.dialogflow; + +// [START dialogflow_detect_intent_streaming] + +import com.google.api.gax.rpc.ApiException; +import com.google.api.gax.rpc.BidiStream; +import com.google.cloud.dialogflow.v2.AudioEncoding; +import com.google.cloud.dialogflow.v2.InputAudioConfig; +import com.google.cloud.dialogflow.v2.QueryInput; +import com.google.cloud.dialogflow.v2.QueryResult; +import com.google.cloud.dialogflow.v2.SessionName; +import com.google.cloud.dialogflow.v2.SessionsClient; +import com.google.cloud.dialogflow.v2.StreamingDetectIntentRequest; +import com.google.cloud.dialogflow.v2.StreamingDetectIntentResponse; +import com.google.protobuf.ByteString; +import java.io.FileInputStream; +import java.io.IOException; + +class DetectIntentStream { + + // DialogFlow API Detect Intent sample with audio files processes as an audio stream. + static void detectIntentStream(String projectId, String audioFilePath, String sessionId) + throws IOException, ApiException { + // String projectId = "YOUR_PROJECT_ID"; + // String audioFilePath = "path_to_your_audio_file"; + // Using the same `sessionId` between requests allows continuation of the conversation. + // String sessionId = "Identifier of the DetectIntent session"; + + // Instantiates a client + try (SessionsClient sessionsClient = SessionsClient.create()) { + // Set the session name using the sessionId (UUID) and projectID (my-project-id) + SessionName session = SessionName.of(projectId, sessionId); + + // Instructs the speech recognizer how to process the audio content. + // Note: hard coding audioEncoding and sampleRateHertz for simplicity. + // Audio encoding of the audio content sent in the query request. + InputAudioConfig inputAudioConfig = + InputAudioConfig.newBuilder() + .setAudioEncoding(AudioEncoding.AUDIO_ENCODING_LINEAR_16) + .setLanguageCode("en-US") // languageCode = "en-US" + .setSampleRateHertz(16000) // sampleRateHertz = 16000 + .build(); + + // Build the query with the InputAudioConfig + QueryInput queryInput = QueryInput.newBuilder().setAudioConfig(inputAudioConfig).build(); + + // Create the Bidirectional stream + BidiStream bidiStream = + sessionsClient.streamingDetectIntentCallable().call(); + + // The first request must **only** contain the audio configuration: + bidiStream.send( + StreamingDetectIntentRequest.newBuilder() + .setSession(session.toString()) + .setQueryInput(queryInput) + .build()); + + try (FileInputStream audioStream = new FileInputStream(audioFilePath)) { + // Subsequent requests must **only** contain the audio data. + // Following messages: audio chunks. We just read the file in fixed-size chunks. In reality + // you would split the user input by time. + byte[] buffer = new byte[4096]; + int bytes; + while ((bytes = audioStream.read(buffer)) != -1) { + bidiStream.send( + StreamingDetectIntentRequest.newBuilder() + .setInputAudio(ByteString.copyFrom(buffer, 0, bytes)) + .build()); + } + } + + // Tell the service you are done sending data + bidiStream.closeSend(); + + for (StreamingDetectIntentResponse response : bidiStream) { + QueryResult queryResult = response.getQueryResult(); + System.out.println("===================="); + System.out.format("Intent Display Name: %s\n", queryResult.getIntent().getDisplayName()); + System.out.format("Query Text: '%s'\n", queryResult.getQueryText()); + System.out.format( + "Detected Intent: %s (confidence: %f)\n", + queryResult.getIntent().getDisplayName(), queryResult.getIntentDetectionConfidence()); + System.out.format( + "Fulfillment Text: '%s'\n", + queryResult.getFulfillmentMessagesCount() > 0 + ? queryResult.getFulfillmentMessages(0).getText() + : "Triggered Default Fallback Intent"); + } + } + } +} +// [END dialogflow_detect_intent_streaming] diff --git a/dialogflow/snippets/src/main/java/com/example/dialogflow/DetectIntentTexts.java b/dialogflow/snippets/src/main/java/com/example/dialogflow/DetectIntentTexts.java new file mode 100644 index 00000000000..0eb5b415bdb --- /dev/null +++ b/dialogflow/snippets/src/main/java/com/example/dialogflow/DetectIntentTexts.java @@ -0,0 +1,78 @@ +/* + * 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.dialogflow; + +// [START dialogflow_detect_intent_text] + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.dialogflow.v2.DetectIntentResponse; +import com.google.cloud.dialogflow.v2.QueryInput; +import com.google.cloud.dialogflow.v2.QueryResult; +import com.google.cloud.dialogflow.v2.SessionName; +import com.google.cloud.dialogflow.v2.SessionsClient; +import com.google.cloud.dialogflow.v2.TextInput; +import com.google.common.collect.Maps; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class DetectIntentTexts { + + // DialogFlow API Detect Intent sample with text inputs. + public static Map detectIntentTexts( + String projectId, List texts, String sessionId, String languageCode) + throws IOException, ApiException { + Map queryResults = Maps.newHashMap(); + // Instantiates a client + try (SessionsClient sessionsClient = SessionsClient.create()) { + // Set the session name using the sessionId (UUID) and projectID (my-project-id) + SessionName session = SessionName.of(projectId, sessionId); + System.out.println("Session Path: " + session.toString()); + + // Detect intents for each text input + for (String text : texts) { + // Set the text (hello) and language code (en-US) for the query + TextInput.Builder textInput = + TextInput.newBuilder().setText(text).setLanguageCode(languageCode); + + // Build the query with the TextInput + QueryInput queryInput = QueryInput.newBuilder().setText(textInput).build(); + + // Performs the detect intent request + DetectIntentResponse response = sessionsClient.detectIntent(session, queryInput); + + // Display the query result + QueryResult queryResult = response.getQueryResult(); + + System.out.println("===================="); + System.out.format("Query Text: '%s'\n", queryResult.getQueryText()); + System.out.format( + "Detected Intent: %s (confidence: %f)\n", + queryResult.getIntent().getDisplayName(), queryResult.getIntentDetectionConfidence()); + System.out.format( + "Fulfillment Text: '%s'\n", + queryResult.getFulfillmentMessagesCount() > 0 + ? queryResult.getFulfillmentMessages(0).getText() + : "Triggered Default Fallback Intent"); + + queryResults.put(text, queryResult); + } + } + return queryResults; + } +} +// [END dialogflow_detect_intent_text] diff --git a/dialogflow/snippets/src/main/java/com/example/dialogflow/DetectIntentWithLocation.java b/dialogflow/snippets/src/main/java/com/example/dialogflow/DetectIntentWithLocation.java new file mode 100644 index 00000000000..6c825812577 --- /dev/null +++ b/dialogflow/snippets/src/main/java/com/example/dialogflow/DetectIntentWithLocation.java @@ -0,0 +1,88 @@ +/* + * 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.dialogflow; + +// [START dialogflow_detect_intent_with_location] + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.dialogflow.v2beta1.DetectIntentResponse; +import com.google.cloud.dialogflow.v2beta1.QueryInput; +import com.google.cloud.dialogflow.v2beta1.QueryResult; +import com.google.cloud.dialogflow.v2beta1.SessionName; +import com.google.cloud.dialogflow.v2beta1.SessionsClient; +import com.google.cloud.dialogflow.v2beta1.SessionsSettings; +import com.google.cloud.dialogflow.v2beta1.TextInput; +import com.google.common.collect.Maps; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class DetectIntentWithLocation { + + // DialogFlow API Detect Intent sample with text inputs. + public static Map detectIntentWithLocation( + String projectId, + String locationId, + List texts, + String sessionId, + String languageCode) + throws IOException, ApiException { + SessionsSettings sessionsSettings = + SessionsSettings.newBuilder() + .setEndpoint(locationId + "-dialogflow.googleapis.com:443") + .build(); + Map queryResults = Maps.newHashMap(); + // Instantiates a client + try (SessionsClient sessionsClient = SessionsClient.create(sessionsSettings)) { + // Set the session name using the projectId (my-project-id), locationId and sessionId (UUID) + SessionName session = + SessionName.ofProjectLocationSessionName(projectId, locationId, sessionId); + System.out.println("Session Path: " + session.toString()); + + // Detect intents for each text input + for (String text : texts) { + // Set the text (hello) and language code (en-US) for the query + TextInput.Builder textInput = + TextInput.newBuilder().setText(text).setLanguageCode(languageCode); + + // Build the query with the TextInput + QueryInput queryInput = QueryInput.newBuilder().setText(textInput).build(); + + // Performs the detect intent request + DetectIntentResponse response = sessionsClient.detectIntent(session, queryInput); + + // Display the query result + QueryResult queryResult = response.getQueryResult(); + + System.out.println("===================="); + System.out.format("Query Text: '%s'\n", queryResult.getQueryText()); + System.out.format( + "Detected Intent: %s (confidence: %f)\n", + queryResult.getIntent().getDisplayName(), queryResult.getIntentDetectionConfidence()); + System.out.format( + "Fulfillment Text: '%s'\n", + queryResult.getFulfillmentMessagesCount() > 0 + ? queryResult.getFulfillmentMessages(0).getText() + : "Triggered Default Fallback Intent"); + + queryResults.put(text, queryResult); + } + } + return queryResults; + } +} +// [END dialogflow_detect_intent_with_location] diff --git a/dialogflow/snippets/src/main/java/com/example/dialogflow/DetectIntentWithSentimentAnalysis.java b/dialogflow/snippets/src/main/java/com/example/dialogflow/DetectIntentWithSentimentAnalysis.java new file mode 100644 index 00000000000..4a9133a3e50 --- /dev/null +++ b/dialogflow/snippets/src/main/java/com/example/dialogflow/DetectIntentWithSentimentAnalysis.java @@ -0,0 +1,98 @@ +/* + * 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.dialogflow; + +// [START dialogflow_detect_intent_with_sentiment_analysis] + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.dialogflow.v2.DetectIntentRequest; +import com.google.cloud.dialogflow.v2.DetectIntentResponse; +import com.google.cloud.dialogflow.v2.QueryInput; +import com.google.cloud.dialogflow.v2.QueryParameters; +import com.google.cloud.dialogflow.v2.QueryResult; +import com.google.cloud.dialogflow.v2.SentimentAnalysisRequestConfig; +import com.google.cloud.dialogflow.v2.SessionName; +import com.google.cloud.dialogflow.v2.SessionsClient; +import com.google.cloud.dialogflow.v2.TextInput; +import com.google.common.collect.Maps; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class DetectIntentWithSentimentAnalysis { + + public static Map detectIntentSentimentAnalysis( + String projectId, List texts, String sessionId, String languageCode) + throws IOException, ApiException { + Map queryResults = Maps.newHashMap(); + // Instantiates a client + try (SessionsClient sessionsClient = SessionsClient.create()) { + // Set the session name using the sessionId (UUID) and projectID (my-project-id) + SessionName session = SessionName.of(projectId, sessionId); + System.out.println("Session Path: " + session.toString()); + + // Detect intents for each text input + for (String text : texts) { + // Set the text (hello) and language code (en-US) for the query + TextInput.Builder textInput = + TextInput.newBuilder().setText(text).setLanguageCode(languageCode); + + // Build the query with the TextInput + QueryInput queryInput = QueryInput.newBuilder().setText(textInput).build(); + + // + SentimentAnalysisRequestConfig sentimentAnalysisRequestConfig = + SentimentAnalysisRequestConfig.newBuilder().setAnalyzeQueryTextSentiment(true).build(); + + QueryParameters queryParameters = + QueryParameters.newBuilder() + .setSentimentAnalysisRequestConfig(sentimentAnalysisRequestConfig) + .build(); + DetectIntentRequest detectIntentRequest = + DetectIntentRequest.newBuilder() + .setSession(session.toString()) + .setQueryInput(queryInput) + .setQueryParams(queryParameters) + .build(); + + // Performs the detect intent request + DetectIntentResponse response = sessionsClient.detectIntent(detectIntentRequest); + + // Display the query result + QueryResult queryResult = response.getQueryResult(); + + System.out.println("===================="); + System.out.format("Query Text: '%s'\n", queryResult.getQueryText()); + System.out.format( + "Detected Intent: %s (confidence: %f)\n", + queryResult.getIntent().getDisplayName(), queryResult.getIntentDetectionConfidence()); + System.out.format( + "Fulfillment Text: '%s'\n", + queryResult.getFulfillmentMessagesCount() > 0 + ? queryResult.getFulfillmentMessages(0).getText() + : "Triggered Default Fallback Intent"); + System.out.format( + "Sentiment Score: '%s'\n", + queryResult.getSentimentAnalysisResult().getQueryTextSentiment().getScore()); + + queryResults.put(text, queryResult); + } + } + return queryResults; + } +} +// [END dialogflow_detect_intent_with_sentiment_analysis] diff --git a/dialogflow/snippets/src/main/java/com/example/dialogflow/DetectIntentWithTextToSpeechResponse.java b/dialogflow/snippets/src/main/java/com/example/dialogflow/DetectIntentWithTextToSpeechResponse.java new file mode 100644 index 00000000000..fbe97131846 --- /dev/null +++ b/dialogflow/snippets/src/main/java/com/example/dialogflow/DetectIntentWithTextToSpeechResponse.java @@ -0,0 +1,96 @@ +/* + * 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.dialogflow; + +// [START dialogflow_detect_intent_with_texttospeech_response] + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.dialogflow.v2.DetectIntentRequest; +import com.google.cloud.dialogflow.v2.DetectIntentResponse; +import com.google.cloud.dialogflow.v2.OutputAudioConfig; +import com.google.cloud.dialogflow.v2.OutputAudioEncoding; +import com.google.cloud.dialogflow.v2.QueryInput; +import com.google.cloud.dialogflow.v2.QueryResult; +import com.google.cloud.dialogflow.v2.SessionName; +import com.google.cloud.dialogflow.v2.SessionsClient; +import com.google.cloud.dialogflow.v2.TextInput; +import com.google.common.collect.Maps; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class DetectIntentWithTextToSpeechResponse { + + public static Map detectIntentWithTexttoSpeech( + String projectId, List texts, String sessionId, String languageCode) + throws IOException, ApiException { + Map queryResults = Maps.newHashMap(); + // Instantiates a client + try (SessionsClient sessionsClient = SessionsClient.create()) { + // Set the session name using the sessionId (UUID) and projectID (my-project-id) + SessionName session = SessionName.of(projectId, sessionId); + System.out.println("Session Path: " + session.toString()); + + // Detect intents for each text input + for (String text : texts) { + // Set the text (hello) and language code (en-US) for the query + TextInput.Builder textInput = + TextInput.newBuilder().setText(text).setLanguageCode(languageCode); + + // Build the query with the TextInput + QueryInput queryInput = QueryInput.newBuilder().setText(textInput).build(); + + // + OutputAudioEncoding audioEncoding = OutputAudioEncoding.OUTPUT_AUDIO_ENCODING_LINEAR_16; + int sampleRateHertz = 16000; + OutputAudioConfig outputAudioConfig = + OutputAudioConfig.newBuilder() + .setAudioEncoding(audioEncoding) + .setSampleRateHertz(sampleRateHertz) + .build(); + + DetectIntentRequest dr = + DetectIntentRequest.newBuilder() + .setQueryInput(queryInput) + .setOutputAudioConfig(outputAudioConfig) + .setSession(session.toString()) + .build(); + + // Performs the detect intent request + DetectIntentResponse response = sessionsClient.detectIntent(dr); + + // Display the query result + QueryResult queryResult = response.getQueryResult(); + + System.out.println("===================="); + System.out.format("Query Text: '%s'\n", queryResult.getQueryText()); + System.out.format( + "Detected Intent: %s (confidence: %f)\n", + queryResult.getIntent().getDisplayName(), queryResult.getIntentDetectionConfidence()); + System.out.format( + "Fulfillment Text: '%s'\n", + queryResult.getFulfillmentMessagesCount() > 0 + ? queryResult.getFulfillmentMessages(0).getText() + : "Triggered Default Fallback Intent"); + + queryResults.put(text, queryResult); + } + } + return queryResults; + } +} +// [END dialogflow_detect_intent_with_texttospeech_response] diff --git a/dialogflow/snippets/src/main/java/com/example/dialogflow/DocumentManagement.java b/dialogflow/snippets/src/main/java/com/example/dialogflow/DocumentManagement.java new file mode 100644 index 00000000000..08431689008 --- /dev/null +++ b/dialogflow/snippets/src/main/java/com/example/dialogflow/DocumentManagement.java @@ -0,0 +1,71 @@ +/* + * 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.dialogflow; + +// [START dialogflow_create_document] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.dialogflow.v2.CreateDocumentRequest; +import com.google.cloud.dialogflow.v2.Document; +import com.google.cloud.dialogflow.v2.Document.KnowledgeType; +import com.google.cloud.dialogflow.v2.DocumentsClient; +import com.google.cloud.dialogflow.v2.KnowledgeOperationMetadata; +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class DocumentManagement { + + public static void createDocument( + String knowledgeBaseName, + String displayName, + String mimeType, + String knowledgeType, + String contentUri) + throws IOException, ApiException, InterruptedException, ExecutionException, TimeoutException { + // Instantiates a client + try (DocumentsClient documentsClient = DocumentsClient.create()) { + Document document = + Document.newBuilder() + .setDisplayName(displayName) + .setContentUri(contentUri) + .setMimeType(mimeType) + .addKnowledgeTypes(KnowledgeType.valueOf(knowledgeType)) + .build(); + CreateDocumentRequest createDocumentRequest = + CreateDocumentRequest.newBuilder() + .setDocument(document) + .setParent(knowledgeBaseName) + .build(); + OperationFuture response = + documentsClient.createDocumentAsync(createDocumentRequest); + Document createdDocument = response.get(180, TimeUnit.SECONDS); + System.out.format("Created Document:\n"); + System.out.format(" - Display Name: %s\n", createdDocument.getDisplayName()); + System.out.format(" - Document Name: %s\n", createdDocument.getName()); + System.out.format(" - MIME Type: %s\n", createdDocument.getMimeType()); + System.out.format(" - Knowledge Types:\n"); + for (KnowledgeType knowledgeTypeId : document.getKnowledgeTypesList()) { + System.out.format(" - %s \n", knowledgeTypeId.getValueDescriptor()); + } + System.out.format(" - Source: %s \n", document.getContentUri()); + } + } +} +// [END dialogflow_create_document] diff --git a/dialogflow/snippets/src/main/java/com/example/dialogflow/IntentManagement.java b/dialogflow/snippets/src/main/java/com/example/dialogflow/IntentManagement.java new file mode 100644 index 00000000000..a9ed0f51880 --- /dev/null +++ b/dialogflow/snippets/src/main/java/com/example/dialogflow/IntentManagement.java @@ -0,0 +1,167 @@ +/* + * 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.dialogflow; + +// Imports the Google Cloud client library + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.dialogflow.v2.AgentName; +import com.google.cloud.dialogflow.v2.Context; +import com.google.cloud.dialogflow.v2.Intent; +import com.google.cloud.dialogflow.v2.Intent.Message; +import com.google.cloud.dialogflow.v2.Intent.Message.Text; +import com.google.cloud.dialogflow.v2.Intent.TrainingPhrase; +import com.google.cloud.dialogflow.v2.Intent.TrainingPhrase.Part; +import com.google.cloud.dialogflow.v2.IntentName; +import com.google.cloud.dialogflow.v2.IntentsClient; +import com.google.common.collect.Lists; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** DialogFlow API Intent sample. */ +public class IntentManagement { + // [START dialogflow_list_intents] + + /** + * List intents + * + * @param projectId Project/Agent Id. + * @return Intents found. + */ + public static List listIntents(String projectId) throws ApiException, IOException { + List intents = Lists.newArrayList(); + // Instantiates a client + try (IntentsClient intentsClient = IntentsClient.create()) { + // Set the project agent name using the projectID (my-project-id) + AgentName parent = AgentName.of(projectId); + + // Performs the list intents request + for (Intent intent : intentsClient.listIntents(parent).iterateAll()) { + System.out.println("===================="); + System.out.format("Intent name: '%s'\n", intent.getName()); + System.out.format("Intent display name: '%s'\n", intent.getDisplayName()); + System.out.format("Action: '%s'\n", intent.getAction()); + System.out.format("Root followup intent: '%s'\n", intent.getRootFollowupIntentName()); + System.out.format("Parent followup intent: '%s'\n", intent.getParentFollowupIntentName()); + + System.out.format("Input contexts:\n"); + for (String inputContextName : intent.getInputContextNamesList()) { + System.out.format("\tName: %s\n", inputContextName); + } + System.out.format("Output contexts:\n"); + for (Context outputContext : intent.getOutputContextsList()) { + System.out.format("\tName: %s\n", outputContext.getName()); + } + + intents.add(intent); + } + } + return intents; + } + // [END dialogflow_list_intents] + + // [START dialogflow_create_intent] + + /** + * Create an intent of the given intent type + * + * @param displayName The display name of the intent. + * @param projectId Project/Agent Id. + * @param trainingPhrasesParts Training phrases. + * @param messageTexts Message texts for the agent's response when the intent is detected. + * @return The created Intent. + */ + public static Intent createIntent( + String displayName, + String projectId, + List trainingPhrasesParts, + List messageTexts) + throws ApiException, IOException { + // Instantiates a client + try (IntentsClient intentsClient = IntentsClient.create()) { + // Set the project agent name using the projectID (my-project-id) + AgentName parent = AgentName.of(projectId); + + // Build the trainingPhrases from the trainingPhrasesParts + List trainingPhrases = new ArrayList<>(); + for (String trainingPhrase : trainingPhrasesParts) { + trainingPhrases.add( + TrainingPhrase.newBuilder() + .addParts(Part.newBuilder().setText(trainingPhrase).build()) + .build()); + } + + // Build the message texts for the agent's response + Message message = + Message.newBuilder().setText(Text.newBuilder().addAllText(messageTexts).build()).build(); + + // Build the intent + Intent intent = + Intent.newBuilder() + .setDisplayName(displayName) + .addMessages(message) + .addAllTrainingPhrases(trainingPhrases) + .build(); + + // Performs the create intent request + Intent response = intentsClient.createIntent(parent, intent); + System.out.format("Intent created: %s\n", response); + + return response; + } + } + // [END dialogflow_create_intent] + + // [START dialogflow_delete_intent] + + /** + * Delete intent with the given intent type and intent value + * + * @param intentId The id of the intent. + * @param projectId Project/Agent Id. + */ + public static void deleteIntent(String intentId, String projectId) + throws ApiException, IOException { + // Instantiates a client + try (IntentsClient intentsClient = IntentsClient.create()) { + IntentName name = IntentName.of(projectId, intentId); + // Performs the delete intent request + intentsClient.deleteIntent(name); + } + } + // [END dialogflow_delete_intent] + + /** Helper method for testing to get intentIds from displayName. */ + public static List getIntentIds(String displayName, String projectId) + throws ApiException, IOException { + List intentIds = new ArrayList<>(); + + // Instantiates a client + try (IntentsClient intentsClient = IntentsClient.create()) { + AgentName parent = AgentName.of(projectId); + for (Intent intent : intentsClient.listIntents(parent).iterateAll()) { + if (intent.getDisplayName().equals(displayName)) { + String[] splitName = intent.getName().split("/"); + intentIds.add(splitName[splitName.length - 1]); + } + } + } + + return intentIds; + } +} diff --git a/dialogflow/snippets/src/main/java/com/example/dialogflow/KnowledgeBaseManagement.java b/dialogflow/snippets/src/main/java/com/example/dialogflow/KnowledgeBaseManagement.java new file mode 100644 index 00000000000..e3d30664a6c --- /dev/null +++ b/dialogflow/snippets/src/main/java/com/example/dialogflow/KnowledgeBaseManagement.java @@ -0,0 +1,58 @@ +/* + * 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.dialogflow; + +// [START dialogflow_create_knowledge_base] + +import com.google.api.gax.rpc.ApiException; +import com.google.cloud.dialogflow.v2.KnowledgeBase; +import com.google.cloud.dialogflow.v2.KnowledgeBasesClient; +import com.google.cloud.dialogflow.v2.LocationName; +import java.io.IOException; + +public class KnowledgeBaseManagement { + + public static void main(String[] args) throws ApiException, IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my-project-id"; + String location = "my-location"; + + // Set display name of the new knowledge base + String knowledgeBaseDisplayName = "my-knowledge-base-display-name"; + + // Create a knowledge base + createKnowledgeBase(projectId, location, knowledgeBaseDisplayName); + } + + // Create a Knowledge base + public static void createKnowledgeBase(String projectId, String location, String displayName) + throws ApiException, IOException { + // Instantiates a client + try (KnowledgeBasesClient knowledgeBasesClient = KnowledgeBasesClient.create()) { + KnowledgeBase targetKnowledgeBase = + KnowledgeBase.newBuilder().setDisplayName(displayName).build(); + LocationName parent = LocationName.of(projectId, location); + KnowledgeBase createdKnowledgeBase = + knowledgeBasesClient.createKnowledgeBase(parent, targetKnowledgeBase); + System.out.println("===================="); + System.out.format("Knowledgebase created:\n"); + System.out.format("Display Name: %s\n", createdKnowledgeBase.getDisplayName()); + System.out.format("Name: %s\n", createdKnowledgeBase.getName()); + } + } +} +// [END dialogflow_create_knowledge_base] diff --git a/dialogflow/snippets/src/main/java/com/example/dialogflow/UpdateIntent.java b/dialogflow/snippets/src/main/java/com/example/dialogflow/UpdateIntent.java new file mode 100644 index 00000000000..8b18afe7e75 --- /dev/null +++ b/dialogflow/snippets/src/main/java/com/example/dialogflow/UpdateIntent.java @@ -0,0 +1,64 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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_es_update_intent] +import com.google.cloud.dialogflow.v2.Intent; +import com.google.cloud.dialogflow.v2.Intent.Builder; +import com.google.cloud.dialogflow.v2.IntentsClient; +import com.google.cloud.dialogflow.v2.UpdateIntentRequest; +import com.google.protobuf.FieldMask; +import java.io.IOException; + +public class UpdateIntent { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my-project-id"; + String intentId = "my-intent-id"; + String location = "my-location"; + String displayName = "my-display-name"; + updateIntent(projectId, intentId, location, displayName); + } + + // DialogFlow API Update Intent sample. + public static void updateIntent( + String projectId, String intentId, String location, String displayName) throws IOException { + try (IntentsClient client = IntentsClient.create()) { + String intentPath = + "projects/" + projectId + "/locations/" + location + "/agent/intents/" + intentId; + + Builder intentBuilder = client.getIntent(intentPath).toBuilder(); + + intentBuilder.setDisplayName(displayName); + FieldMask fieldMask = FieldMask.newBuilder().addPaths("display_name").build(); + + Intent intent = intentBuilder.build(); + UpdateIntentRequest request = + UpdateIntentRequest.newBuilder() + .setIntent(intent) + .setLanguageCode("en") + .setUpdateMask(fieldMask) + .build(); + + // Make API request to update intent using fieldmask + Intent response = client.updateIntent(request); + System.out.println(response); + } + } +} +// [END dialogflow_es_update_intent] diff --git a/dialogflow/snippets/src/test/dialogflow/ExampleIT.java b/dialogflow/snippets/src/test/dialogflow/ExampleIT.java new file mode 100644 index 00000000000..3a64c266070 --- /dev/null +++ b/dialogflow/snippets/src/test/dialogflow/ExampleIT.java @@ -0,0 +1,70 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dialogflow; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + +import com.google.cloud.functions.HttpRequest; +import com.google.cloud.functions.HttpResponse; +import com.google.gson.Gson; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class ExampleIT { + @Mock private HttpRequest request; + @Mock private HttpResponse response; + + private BufferedWriter writerOut; + private StringWriter responseOut; + private static final Gson gson = new Gson(); + + @Before + public void beforeTest() throws IOException { + MockitoAnnotations.initMocks(this); + + // use an empty string as the default request content + BufferedReader reader = new BufferedReader(new StringReader("")); + when(request.getReader()).thenReturn(reader); + + responseOut = new StringWriter(); + writerOut = new BufferedWriter(responseOut); + when(response.getWriter()).thenReturn(writerOut); + } + + @Test + public void helloHttp_bodyParamsPost() throws IOException { + BufferedReader jsonReader = + new BufferedReader( + new StringReader( + "{'queryResult': { 'intent': { 'name': 'projects', 'displayName': 'Default Welcome Intent' } } })")); + + when(request.getReader()).thenReturn(jsonReader); + + new Webhook().service(request, response); + writerOut.flush(); + + assertThat(responseOut.toString()).contains("Hello from a Java GCF Webhook"); + } +} diff --git a/dialogflow/snippets/src/test/dialogflow/SetAgentIT.java b/dialogflow/snippets/src/test/dialogflow/SetAgentIT.java new file mode 100644 index 00000000000..cf7417ca263 --- /dev/null +++ b/dialogflow/snippets/src/test/dialogflow/SetAgentIT.java @@ -0,0 +1,32 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dialogflow; + +import org.junit.Assert; +import org.junit.Test; + +public class SetAgentIT { + /* + * We cannot test setAgent because Dialogflow ES can only have one agent + * and if we create a agent it will delete the exisitng testing agent and + * would cause all tests to fail + */ + @Test + public void testCreateAgent() { + Assert.assertTrue(true); + } +} diff --git a/dialogflow/snippets/src/test/java/com/example/dialogflow/CreateDocumentTest.java b/dialogflow/snippets/src/test/java/com/example/dialogflow/CreateDocumentTest.java new file mode 100644 index 00000000000..e59508a521e --- /dev/null +++ b/dialogflow/snippets/src/test/java/com/example/dialogflow/CreateDocumentTest.java @@ -0,0 +1,109 @@ +/* + * 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.dialogflow; + +import static com.google.common.truth.Truth.assertThat; +import static junit.framework.TestCase.assertNotNull; + +import com.google.cloud.dialogflow.v2.DeleteKnowledgeBaseRequest; +import com.google.cloud.dialogflow.v2.KnowledgeBase; +import com.google.cloud.dialogflow.v2.KnowledgeBasesClient; +import com.google.cloud.dialogflow.v2.LocationName; +import com.google.cloud.testing.junit4.MultipleAttemptsRule; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.UUID; +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) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class CreateDocumentTest { + + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String LOCATION = "global"; + private static String KNOWLEDGE_DISPLAY_NAME = UUID.randomUUID().toString(); + private static String DOCUMENT_DISPLAY_NAME = UUID.randomUUID().toString(); + private String knowledgeBaseName; + private ByteArrayOutputStream bout; + private PrintStream newOutputStream; + private PrintStream originalOutputStream; + + private static void requireEnvVar(String varName) { + assertNotNull(String.format(varName)); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() throws IOException { + originalOutputStream = System.out; + bout = new ByteArrayOutputStream(); + newOutputStream = new PrintStream(bout); + System.setOut(newOutputStream); + + // Create a knowledge base for the document + try (KnowledgeBasesClient client = KnowledgeBasesClient.create()) { + KnowledgeBase knowledgeBase = + KnowledgeBase.newBuilder().setDisplayName(KNOWLEDGE_DISPLAY_NAME).build(); + LocationName parent = LocationName.of(PROJECT_ID, LOCATION); + KnowledgeBase response = client.createKnowledgeBase(parent, knowledgeBase); + // Save the full name for deletion + knowledgeBaseName = response.getName(); + } + } + + @After + public void tearDown() throws IOException { + if (knowledgeBaseName == null) { + return; + } + + // Delete the created knowledge base + try (KnowledgeBasesClient client = KnowledgeBasesClient.create()) { + DeleteKnowledgeBaseRequest request = + DeleteKnowledgeBaseRequest.newBuilder().setName(knowledgeBaseName).setForce(true).build(); + client.deleteKnowledgeBase(request); + } + + System.setOut(originalOutputStream); + } + + @Rule public MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); + + @Test + public void testCreateDocument() throws Exception { + DocumentManagement.createDocument( + knowledgeBaseName, + DOCUMENT_DISPLAY_NAME, + "text/html", + "FAQ", + "https://cloud.google.com/storage/docs/faq"); + String got = bout.toString(); + assertThat(got).contains(DOCUMENT_DISPLAY_NAME); + } +} diff --git a/dialogflow/snippets/src/test/java/com/example/dialogflow/CreateKnowledgeBaseTest.java b/dialogflow/snippets/src/test/java/com/example/dialogflow/CreateKnowledgeBaseTest.java new file mode 100644 index 00000000000..a8e00f5e9e7 --- /dev/null +++ b/dialogflow/snippets/src/test/java/com/example/dialogflow/CreateKnowledgeBaseTest.java @@ -0,0 +1,94 @@ +/* + * 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.dialogflow; + +import static com.google.common.truth.Truth.assertThat; +import static junit.framework.TestCase.assertNotNull; + +import com.google.cloud.dialogflow.v2.DeleteKnowledgeBaseRequest; +import com.google.cloud.dialogflow.v2.KnowledgeBasesClient; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +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 CreateKnowledgeBaseTest { + + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String LOCATION = "global"; + private static final String ID_PREFIX_IN_OUTPUT = "Name: "; + private static String KNOWLEDGE_DISPLAY_NAME = UUID.randomUUID().toString(); + private String knowledgeBaseName; + private ByteArrayOutputStream bout; + private PrintStream newOutputStream; + private PrintStream originalOutputStream; + + private static void requireEnvVar(String varName) { + assertNotNull(System.getenv(varName)); + } + + // Extract the name of created resource from "Name: %s\n" in sample code output + private static String getResourceNameFromOutputString(String output) { + return output.substring( + output.lastIndexOf(ID_PREFIX_IN_OUTPUT) + ID_PREFIX_IN_OUTPUT.length(), + output.length() - 1); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + newOutputStream = new PrintStream(bout); + System.setOut(newOutputStream); + } + + @After + public void tearDown() throws IOException { + if (knowledgeBaseName == null) { + return; + } + + // Delete the created knowledge base + try (KnowledgeBasesClient client = KnowledgeBasesClient.create()) { + DeleteKnowledgeBaseRequest request = + DeleteKnowledgeBaseRequest.newBuilder().setName(knowledgeBaseName).setForce(true).build(); + client.deleteKnowledgeBase(request); + } + System.setOut(originalOutputStream); + } + + @Test + public void testCreateKnowledgeBase() throws Exception { + KnowledgeBaseManagement.createKnowledgeBase(PROJECT_ID, LOCATION, KNOWLEDGE_DISPLAY_NAME); + String output = bout.toString(); + assertThat(output).contains(KNOWLEDGE_DISPLAY_NAME); + knowledgeBaseName = getResourceNameFromOutputString(output); + } +} diff --git a/dialogflow/snippets/src/test/java/com/example/dialogflow/DetectIntentKnowledgeTest.java b/dialogflow/snippets/src/test/java/com/example/dialogflow/DetectIntentKnowledgeTest.java new file mode 100644 index 00000000000..0f1aa54d880 --- /dev/null +++ b/dialogflow/snippets/src/test/java/com/example/dialogflow/DetectIntentKnowledgeTest.java @@ -0,0 +1,100 @@ +/* + * 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.dialogflow; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; + +import com.google.cloud.dialogflow.v2beta1.DocumentName; +import com.google.cloud.dialogflow.v2beta1.KnowledgeAnswers; +import com.google.cloud.dialogflow.v2beta1.KnowledgeAnswers.Answer; +import com.google.cloud.dialogflow.v2beta1.KnowledgeBaseName; +import com.google.common.collect.ImmutableList; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class DetectIntentKnowledgeTest { + + 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"; + private static String SESSION_ID = UUID.randomUUID().toString(); + private static String LANGUAGE_CODE = "en-US"; + + private static List TEXTS = + ImmutableList.of( + "How do I sign up?", + "Is my data redundant?", + "Where can I find pricing information?", + "Where is my data stored?", + "What are my support options?", + "How can I maximize the availability of my data?"); + + @Before + public void setUp() { + System.setOut(new PrintStream(new ByteArrayOutputStream())); + } + + @After + public void tearDown() { + System.setOut(null); + } + + @Test + public void testDetectIntentKnowledge() throws Exception { + KnowledgeBaseName knowledgeBaseName = + KnowledgeBaseName.newBuilder() + .setProject(PROJECT_ID) + .setKnowledgeBase(TEST_KNOWLEDGE_BASE_ID) + .build(); + + DocumentName documentName = + DocumentName.newBuilder() + .setProject(PROJECT_ID) + .setKnowledgeBase(TEST_KNOWLEDGE_BASE_ID) + .setDocument(TEST_DOCUMENT_ID) + .build(); + + Map allAnswers = + DetectIntentKnowledge.detectIntentKnowledge( + PROJECT_ID, knowledgeBaseName.toString(), SESSION_ID, LANGUAGE_CODE, TEXTS); + assertEquals(TEXTS.size(), allAnswers.size()); + int answersFound = 0; + for (String text : TEXTS) { + KnowledgeAnswers knowledgeAnswers = allAnswers.get(text); + if (knowledgeAnswers.getAnswersCount() > 0) { + Answer answer = knowledgeAnswers.getAnswers(0); + if (text.equals(answer.getFaqQuestion()) + && documentName.toString().equals(answer.getSource())) { + answersFound++; + } + } + } + // To make the test less flaky, check that half of the texts got a result. + assertThat(answersFound).isGreaterThan(TEXTS.size() / 2); + } +} diff --git a/dialogflow/snippets/src/test/java/com/example/dialogflow/DetectIntentStreamIT.java b/dialogflow/snippets/src/test/java/com/example/dialogflow/DetectIntentStreamIT.java new file mode 100644 index 00000000000..a29f03be3a8 --- /dev/null +++ b/dialogflow/snippets/src/test/java/com/example/dialogflow/DetectIntentStreamIT.java @@ -0,0 +1,62 @@ +/* + * 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.dialogflow; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.UUID; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Integration (system) tests for {@link DetectIntentStream}. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class DetectIntentStreamIT { + + private static String audioFilePath = "resources/book_a_room.wav"; + private static String PROJECT_ID = System.getenv().get("GOOGLE_CLOUD_PROJECT"); + private static String SESSION_ID = UUID.randomUUID().toString(); + private ByteArrayOutputStream bout; + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + System.setOut(new PrintStream(bout)); + } + + @After + public void tearDown() { + System.setOut(null); + bout.reset(); + } + + @Test + public void testStreamingDetectIntentCallable() throws IOException { + DetectIntentStream.detectIntentStream(PROJECT_ID, audioFilePath, SESSION_ID); + + String output = bout.toString(); + + assertThat(output).contains("Intent Display Name: room.reservation"); + assertThat(output).contains("book"); + } +} diff --git a/dialogflow/snippets/src/test/java/com/example/dialogflow/DetectIntentWithAudioTest.java b/dialogflow/snippets/src/test/java/com/example/dialogflow/DetectIntentWithAudioTest.java new file mode 100644 index 00000000000..51649607304 --- /dev/null +++ b/dialogflow/snippets/src/test/java/com/example/dialogflow/DetectIntentWithAudioTest.java @@ -0,0 +1,94 @@ +/* + * 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.dialogflow; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class DetectIntentWithAudioTest { + 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"; + protected static List QUESTIONS = + ImmutableList.of( + "What date?", + "What time will the meeting start?", + "How long will it last?", + "Thanks. How many people are attending?", + "I can help with that. Where would you like to reserve a room?"); + protected static Map ANSWERS = + ImmutableMap.of( + "I can help with that. Where would you like to reserve a room?", + "resources/mountain_view.wav", + "What date?", + "resources/today.wav", + "What time will the meeting start?", + "resources/230pm.wav", + "How long will it last?", + "resources/half_an_hour.wav", + "Thanks. How many people are attending?", + "resources/two_people.wav"); + + @Before + public void setUp() { + System.setOut(new PrintStream(new ByteArrayOutputStream())); + } + + @After + public void tearDown() { + System.setOut(null); + } + + @Test + public void testDetectIntentAudio() throws Exception { + List askedQuestions = Lists.newArrayList(); + com.google.cloud.dialogflow.v2.QueryResult result = + DetectIntentAudio.detectIntentAudio( + PROJECT_ID, "resources/book_a_room.wav", SESSION_ID, LANGUAGE_CODE); + String fulfillmentText = result.getFulfillmentText(); + while (!result.getAllRequiredParamsPresent() + && ANSWERS.containsKey(fulfillmentText) + && !askedQuestions.contains(fulfillmentText)) { + askedQuestions.add(result.getFulfillmentText()); + assertEquals("room.reservation", result.getAction()); + assertThat(QUESTIONS).contains(fulfillmentText); + result = + DetectIntentAudio.detectIntentAudio( + PROJECT_ID, ANSWERS.get(fulfillmentText), SESSION_ID, LANGUAGE_CODE); + fulfillmentText = result.getFulfillmentText(); + } + assertTrue(result.getAllRequiredParamsPresent()); + assertEquals("Choose a room please.", fulfillmentText); + } +} diff --git a/dialogflow/snippets/src/test/java/com/example/dialogflow/DetectIntentWithSentimentAndTextToSpeechIT.java b/dialogflow/snippets/src/test/java/com/example/dialogflow/DetectIntentWithSentimentAndTextToSpeechIT.java new file mode 100644 index 00000000000..64962cc77de --- /dev/null +++ b/dialogflow/snippets/src/test/java/com/example/dialogflow/DetectIntentWithSentimentAndTextToSpeechIT.java @@ -0,0 +1,106 @@ +/* + * 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.dialogflow; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.google.cloud.dialogflow.v2.QueryResult; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Integration (system) tests for {@link DetectIntentWithSentimentAnalysis}. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class DetectIntentWithSentimentAndTextToSpeechIT { + + private static String PROJECT_ID = System.getenv().get("GOOGLE_CLOUD_PROJECT"); + private static String LOCATION_ID = "asia-northeast1"; + private static String SESSION_ID = UUID.randomUUID().toString(); + private static String LANGUAGE_CODE = "en-US"; + private static List TEXTS = + Arrays.asList( + "hello", + "book a meeting room", + "Mountain View", + "tomorrow", + "10 am", + "2 hours", + "10 people", + "A", + "yes"); + + @Before + public void setUp() { + System.setOut(new PrintStream(new ByteArrayOutputStream())); + } + + @After + public void tearDown() { + System.setOut(null); + } + + @Test + public void testDetectIntentTexts() throws Exception { + Map queryResults = + DetectIntentTexts.detectIntentTexts(PROJECT_ID, TEXTS, SESSION_ID, LANGUAGE_CODE); + com.google.cloud.dialogflow.v2.QueryResult finalResult = + queryResults.get(TEXTS.get(TEXTS.size() - 1)); + assertTrue(finalResult.getAllRequiredParamsPresent()); + assertEquals("All set!", finalResult.getFulfillmentText()); + } + + @Test + public void testDetectIntentTextsWithLocation() throws Exception { + Map queryResults = + DetectIntentWithLocation.detectIntentWithLocation( + PROJECT_ID, LOCATION_ID, TEXTS, SESSION_ID, LANGUAGE_CODE); + com.google.cloud.dialogflow.v2beta1.QueryResult finalResult = + queryResults.get(TEXTS.get(TEXTS.size() - 1)); + assertTrue(finalResult.getAllRequiredParamsPresent()); + assertEquals("All set!", finalResult.getFulfillmentText()); + } + + @Test + public void testDetectIntentWithSentimentAnalysis() throws Exception { + assertResults( + DetectIntentWithSentimentAnalysis.detectIntentSentimentAnalysis( + PROJECT_ID, TEXTS, SESSION_ID, LANGUAGE_CODE)); + } + + @Test + public void testDetectIntentTextToSpeech() throws Exception { + assertResults( + DetectIntentWithTextToSpeechResponse.detectIntentWithTexttoSpeech( + PROJECT_ID, TEXTS, SESSION_ID, LANGUAGE_CODE)); + } + + private void assertResults(Map queryResults) { + QueryResult finalResult = queryResults.get(TEXTS.get(TEXTS.size() - 1)); + assertTrue(finalResult.getAllRequiredParamsPresent()); + assertEquals("All set!", finalResult.getFulfillmentText()); + } +} diff --git a/dialogflow/snippets/src/test/java/com/example/dialogflow/IntentManagementIT.java b/dialogflow/snippets/src/test/java/com/example/dialogflow/IntentManagementIT.java new file mode 100644 index 00000000000..09bdf8299dc --- /dev/null +++ b/dialogflow/snippets/src/test/java/com/example/dialogflow/IntentManagementIT.java @@ -0,0 +1,98 @@ +/* + * 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.dialogflow; + +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.cloud.dialogflow.v2.AgentName; +import com.google.cloud.dialogflow.v2.Intent; +import com.google.cloud.dialogflow.v2.IntentsClient; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Integration (system) tests for {@link IntentManagement}. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class IntentManagementIT { + private static String INTENT_DISPLAY_NAME = UUID.randomUUID().toString(); + private static List MESSAGE_TEXTS = + Arrays.asList("fake_message_text_for_testing_1", "fake_message_text_for_testing_2"); + private static List TRAINING_PHRASE_PARTS = + Arrays.asList("fake_training_phrase_part_1", "fake_training_phrase_part_2"); + private static String PROJECT_ID = System.getenv().get("GOOGLE_CLOUD_PROJECT"); + + @Before + public void setUp() { + System.setOut(new PrintStream(new ByteArrayOutputStream())); + } + + @After + public void tearDown() throws Exception { + try (IntentsClient intentsClient = IntentsClient.create()) { + // Set the project agent name using the projectID (my-project-id) + AgentName parent = AgentName.of(PROJECT_ID); + + // Performs the list intents request + for (Intent intent : intentsClient.listIntents(parent).iterateAll()) { + if (intent.getDisplayName().equals(INTENT_DISPLAY_NAME)) { + intentsClient.deleteIntent(intent.getName()); + } + } + } + System.setOut(null); + } + + @Test + public void testCreateIntent() throws Exception { + // Create the intent + Intent intent = + IntentManagement.createIntent( + INTENT_DISPLAY_NAME, PROJECT_ID, TRAINING_PHRASE_PARTS, MESSAGE_TEXTS); + assertNotNull(intent); + + List intentIds = IntentManagement.getIntentIds(intent.getDisplayName(), PROJECT_ID); + assertThat(intentIds.size()).isEqualTo(1); + + List intents = IntentManagement.listIntents(PROJECT_ID); + assertTrue(intents.size() > 0); + assertThat(intents).contains(intent); + for (String messageText : MESSAGE_TEXTS) { + assertTrue( + intent.getMessagesList().stream() + .anyMatch(message -> message.getText().toString().contains(messageText))); + } + + for (String intentId : intentIds) { + IntentManagement.deleteIntent(intentId, PROJECT_ID); + } + + int numIntents = intents.size(); + intents = IntentManagement.listIntents(PROJECT_ID); + assertEquals(numIntents - 1, intents.size()); + } +} diff --git a/dialogflow/snippets/src/test/java/com/example/dialogflow/UpdateIntentIT.java b/dialogflow/snippets/src/test/java/com/example/dialogflow/UpdateIntentIT.java new file mode 100644 index 00000000000..84d9cfb08fd --- /dev/null +++ b/dialogflow/snippets/src/test/java/com/example/dialogflow/UpdateIntentIT.java @@ -0,0 +1,78 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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 com.google.cloud.dialogflow.v2.Intent; +import com.google.cloud.dialogflow.v2.IntentsClient; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.UUID; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class UpdateIntentIT { + + private static String PROJECT_ID = System.getenv().get("GOOGLE_CLOUD_PROJECT"); + + private static String parent = "projects/" + PROJECT_ID + "/locations/global/agent"; + private static String intentID = ""; + private static String intentPath = ""; + + private ByteArrayOutputStream stdOut; + + @Before + public void setUp() throws IOException { + + stdOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdOut)); + + try (IntentsClient intentsClient = IntentsClient.create()) { + com.google.cloud.dialogflow.v2.Intent.Builder intent = Intent.newBuilder(); + intent.setDisplayName("temp_intent_" + UUID.randomUUID().toString()); + + UpdateIntentIT.intentPath = intentsClient.createIntent(parent, intent.build()).getName(); + UpdateIntentIT.intentID = UpdateIntentIT.intentPath.split("/")[6]; + } + } + + @After + public void tearDown() throws IOException { + stdOut = null; + System.setOut(null); + + IntentsClient client = IntentsClient.create(); + + String intentPath = + "projects/" + PROJECT_ID + "/locations/global/agent/intents/" + UpdateIntentIT.intentID; + + client.deleteIntent(intentPath); + } + + @Test + public void testUpdateIntent() throws IOException { + + String fakeIntent = "fake_intent_" + UUID.randomUUID().toString(); + + UpdateIntent.updateIntent(PROJECT_ID, UpdateIntentIT.intentID, "global", fakeIntent); + + assertThat(stdOut.toString()).contains(fakeIntent); + } +} diff --git a/document-ai/pom.xml b/document-ai/pom.xml new file mode 100644 index 00000000000..3cd45cc7ac9 --- /dev/null +++ b/document-ai/pom.xml @@ -0,0 +1,64 @@ + + + 4.0.0 + com.example.documentai + documentai-snippets + jar + Google Document AI Snippets + https://github.com/GoogleCloudPlatform/java-docs-samples/tree/main/document-ai + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + 1.8 + 1.8 + UTF-8 + + + + + + + + com.google.cloud + libraries-bom + 26.1.3 + pom + import + + + + + + + com.google.cloud + google-cloud-document-ai + 2.7.5 + + + + com.google.cloud + google-cloud-storage + + + junit + junit + 4.13.2 + test + + + com.google.truth + truth + 1.1.3 + test + + + diff --git a/document-ai/resources/document_quality_poor.pdf b/document-ai/resources/document_quality_poor.pdf new file mode 100644 index 00000000000..3a34a925c04 Binary files /dev/null and b/document-ai/resources/document_quality_poor.pdf differ diff --git a/document-ai/resources/handwritten_form.pdf b/document-ai/resources/handwritten_form.pdf new file mode 100644 index 00000000000..2189ffffd00 Binary files /dev/null and b/document-ai/resources/handwritten_form.pdf differ diff --git a/document-ai/resources/invoice.pdf b/document-ai/resources/invoice.pdf new file mode 100644 index 00000000000..7722734a430 Binary files /dev/null and b/document-ai/resources/invoice.pdf differ diff --git a/document-ai/resources/multi_document.pdf b/document-ai/resources/multi_document.pdf new file mode 100644 index 00000000000..7ea62eb8f78 Binary files /dev/null and b/document-ai/resources/multi_document.pdf differ diff --git a/document-ai/resources/us_driver_license.pdf b/document-ai/resources/us_driver_license.pdf new file mode 100644 index 00000000000..f8f62d902ee Binary files /dev/null and b/document-ai/resources/us_driver_license.pdf differ diff --git a/document-ai/src/main/java/documentai/v1/BatchProcessDocument.java b/document-ai/src/main/java/documentai/v1/BatchProcessDocument.java new file mode 100644 index 00000000000..efee05e61ec --- /dev/null +++ b/document-ai/src/main/java/documentai/v1/BatchProcessDocument.java @@ -0,0 +1,178 @@ +/* + * 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 documentai.v1; + +// [START documentai_batch_process_document] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.api.gax.paging.Page; +import com.google.cloud.documentai.v1.BatchDocumentsInputConfig; +import com.google.cloud.documentai.v1.BatchProcessMetadata; +import com.google.cloud.documentai.v1.BatchProcessRequest; +import com.google.cloud.documentai.v1.BatchProcessResponse; +import com.google.cloud.documentai.v1.Document; +import com.google.cloud.documentai.v1.DocumentOutputConfig; +import com.google.cloud.documentai.v1.DocumentOutputConfig.GcsOutputConfig; +import com.google.cloud.documentai.v1.DocumentProcessorServiceClient; +import com.google.cloud.documentai.v1.GcsDocument; +import com.google.cloud.documentai.v1.GcsDocuments; +import com.google.cloud.storage.Blob; +import com.google.cloud.storage.BlobId; +import com.google.cloud.storage.Bucket; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import com.google.protobuf.util.JsonFormat; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class BatchProcessDocument { + public static void batchProcessDocument() + throws IOException, InterruptedException, TimeoutException, ExecutionException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String location = "your-project-location"; // Format is "us" or "eu". + String processerId = "your-processor-id"; + String outputGcsBucketName = "your-gcs-bucket-name"; + String outputGcsPrefix = "PREFIX"; + String inputGcsUri = "gs://your-gcs-bucket/path/to/input/file.pdf"; + batchProcessDocument( + projectId, location, processerId, inputGcsUri, outputGcsBucketName, outputGcsPrefix); + } + + public static void batchProcessDocument( + String projectId, + String location, + String processorId, + String gcsInputUri, + String gcsOutputBucketName, + String gcsOutputUriPrefix) + throws IOException, InterruptedException, TimeoutException, ExecutionException { + // 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 (DocumentProcessorServiceClient client = DocumentProcessorServiceClient.create()) { + // The full resource name of the processor, e.g.: + // projects/project-id/locations/location/processor/processor-id + // You must create new processors in the Cloud Console first + String name = + String.format("projects/%s/locations/%s/processors/%s", projectId, location, processorId); + + GcsDocument gcsDocument = + GcsDocument.newBuilder().setGcsUri(gcsInputUri).setMimeType("application/pdf").build(); + + GcsDocuments gcsDocuments = GcsDocuments.newBuilder().addDocuments(gcsDocument).build(); + + BatchDocumentsInputConfig inputConfig = + BatchDocumentsInputConfig.newBuilder().setGcsDocuments(gcsDocuments).build(); + + String fullGcsPath = String.format("gs://%s/%s/", gcsOutputBucketName, gcsOutputUriPrefix); + GcsOutputConfig gcsOutputConfig = GcsOutputConfig.newBuilder().setGcsUri(fullGcsPath).build(); + + DocumentOutputConfig documentOutputConfig = + DocumentOutputConfig.newBuilder().setGcsOutputConfig(gcsOutputConfig).build(); + + // Configure the batch process request. + BatchProcessRequest request = + BatchProcessRequest.newBuilder() + .setName(name) + .setInputDocuments(inputConfig) + .setDocumentOutputConfig(documentOutputConfig) + .build(); + + OperationFuture future = + client.batchProcessDocumentsAsync(request); + + // Batch process document using a long-running operation. + // You can wait for now, or get results later. + // Note: first request to the service takes longer than subsequent + // requests. + System.out.println("Waiting for operation to complete..."); + future.get(240, TimeUnit.SECONDS); + + System.out.println("Document processing complete."); + + Storage storage = StorageOptions.newBuilder().setProjectId(projectId).build().getService(); + Bucket bucket = storage.get(gcsOutputBucketName); + + // List all of the files in the Storage bucket. + Page blobs = bucket.list(Storage.BlobListOption.prefix(gcsOutputUriPrefix + "/")); + int idx = 0; + for (Blob blob : blobs.iterateAll()) { + if (!blob.isDirectory()) { + System.out.printf("Fetched file #%d\n", ++idx); + // Read the results + + // Download and store json data in a temp file. + File tempFile = File.createTempFile("file", ".json"); + Blob fileInfo = storage.get(BlobId.of(gcsOutputBucketName, blob.getName())); + fileInfo.downloadTo(tempFile.toPath()); + + // Parse json file into Document. + FileReader reader = new FileReader(tempFile); + Document.Builder builder = Document.newBuilder(); + JsonFormat.parser().merge(reader, builder); + + Document document = builder.build(); + + // Get all of the document text as one big string. + String text = document.getText(); + + // Read the text recognition output from the processor + System.out.println("The document contains the following paragraphs:"); + Document.Page page1 = document.getPages(0); + List paragraphList = page1.getParagraphsList(); + for (Document.Page.Paragraph paragraph : paragraphList) { + String paragraphText = getText(paragraph.getLayout().getTextAnchor(), text); + System.out.printf("Paragraph text:%s\n", paragraphText); + } + + // Form parsing provides additional output about + // form-formatted PDFs. You must create a form + // processor in the Cloud Console to see full field details. + System.out.println("The following form key/value pairs were detected:"); + + for (Document.Page.FormField field : page1.getFormFieldsList()) { + String fieldName = getText(field.getFieldName().getTextAnchor(), text); + String fieldValue = getText(field.getFieldValue().getTextAnchor(), text); + + System.out.println("Extracted form fields pair:"); + System.out.printf("\t(%s, %s))", fieldName, fieldValue); + } + + // Clean up temp file. + tempFile.deleteOnExit(); + } + } + } + } + + // Extract shards from the text field + private static String getText(Document.TextAnchor textAnchor, String text) { + if (textAnchor.getTextSegmentsList().size() > 0) { + int startIdx = (int) textAnchor.getTextSegments(0).getStartIndex(); + int endIdx = (int) textAnchor.getTextSegments(0).getEndIndex(); + return text.substring(startIdx, endIdx); + } + return "[NO TEXT]"; + } +} +// [END documentai_batch_process_document] diff --git a/document-ai/src/main/java/documentai/v1/ProcessDocument.java b/document-ai/src/main/java/documentai/v1/ProcessDocument.java new file mode 100644 index 00000000000..75a5c639183 --- /dev/null +++ b/document-ai/src/main/java/documentai/v1/ProcessDocument.java @@ -0,0 +1,113 @@ +/* + * 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 documentai.v1; + +// [START documentai_process_document] + +import com.google.cloud.documentai.v1.Document; +import com.google.cloud.documentai.v1.DocumentProcessorServiceClient; +import com.google.cloud.documentai.v1.ProcessRequest; +import com.google.cloud.documentai.v1.ProcessResponse; +import com.google.cloud.documentai.v1.RawDocument; +import com.google.protobuf.ByteString; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +public class ProcessDocument { + public static void processDocument() + throws IOException, InterruptedException, ExecutionException, TimeoutException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String location = "your-project-location"; // Format is "us" or "eu". + String processerId = "your-processor-id"; + String filePath = "path/to/input/file.pdf"; + processDocument(projectId, location, processerId, filePath); + } + + public static void processDocument( + String projectId, String location, String processorId, String filePath) + throws IOException, InterruptedException, ExecutionException, 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 (DocumentProcessorServiceClient client = DocumentProcessorServiceClient.create()) { + // The full resource name of the processor, e.g.: + // projects/project-id/locations/location/processor/processor-id + // You must create new processors in the Cloud Console first + String name = + String.format("projects/%s/locations/%s/processors/%s", projectId, location, processorId); + + // Read the file. + byte[] imageFileData = Files.readAllBytes(Paths.get(filePath)); + + // Convert the image data to a Buffer and base64 encode it. + ByteString content = ByteString.copyFrom(imageFileData); + + RawDocument document = + RawDocument.newBuilder().setContent(content).setMimeType("application/pdf").build(); + + // Configure the process request. + ProcessRequest request = + ProcessRequest.newBuilder().setName(name).setRawDocument(document).build(); + + // Recognizes text entities in the PDF document + ProcessResponse result = client.processDocument(request); + Document documentResponse = result.getDocument(); + + // Get all of the document text as one big string + String text = documentResponse.getText(); + + // Read the text recognition output from the processor + System.out.println("The document contains the following paragraphs:"); + Document.Page firstPage = documentResponse.getPages(0); + List paragraphs = firstPage.getParagraphsList(); + + for (Document.Page.Paragraph paragraph : paragraphs) { + String paragraphText = getText(paragraph.getLayout().getTextAnchor(), text); + System.out.printf("Paragraph text:\n%s\n", paragraphText); + } + + // Form parsing provides additional output about + // form-formatted PDFs. You must create a form + // processor in the Cloud Console to see full field details. + System.out.println("The following form key/value pairs were detected:"); + + for (Document.Page.FormField field : firstPage.getFormFieldsList()) { + String fieldName = getText(field.getFieldName().getTextAnchor(), text); + String fieldValue = getText(field.getFieldValue().getTextAnchor(), text); + + System.out.println("Extracted form fields pair:"); + System.out.printf("\t(%s, %s))\n", fieldName, fieldValue); + } + } + } + + // Extract shards from the text field + private static String getText(Document.TextAnchor textAnchor, String text) { + if (textAnchor.getTextSegmentsList().size() > 0) { + int startIdx = (int) textAnchor.getTextSegments(0).getStartIndex(); + int endIdx = (int) textAnchor.getTextSegments(0).getEndIndex(); + return text.substring(startIdx, endIdx); + } + return "[NO TEXT]"; + } +} +// [END documentai_process_document] diff --git a/document-ai/src/main/java/documentai/v1/QuickStart.java b/document-ai/src/main/java/documentai/v1/QuickStart.java new file mode 100644 index 00000000000..88f22136a5f --- /dev/null +++ b/document-ai/src/main/java/documentai/v1/QuickStart.java @@ -0,0 +1,99 @@ +/* + * 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 documentai.v1; + +// [START documentai_quickstart] +import com.google.cloud.documentai.v1.Document; +import com.google.cloud.documentai.v1.DocumentProcessorServiceClient; +import com.google.cloud.documentai.v1.ProcessRequest; +import com.google.cloud.documentai.v1.ProcessResponse; +import com.google.cloud.documentai.v1.RawDocument; +import com.google.protobuf.ByteString; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +public class QuickStart { + public static void main(String[] args) + throws IOException, InterruptedException, ExecutionException, TimeoutException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String location = "your-project-location"; // Format is "us" or "eu". + String processorId = "your-processor-id"; + String filePath = "path/to/input/file.pdf"; + quickStart(projectId, location, processorId, filePath); + } + + public static void quickStart( + String projectId, String location, String processorId, String filePath) + throws IOException, InterruptedException, ExecutionException, 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 (DocumentProcessorServiceClient client = DocumentProcessorServiceClient.create()) { + // The full resource name of the processor, e.g.: + // projects/project-id/locations/location/processor/processor-id + // You must create new processors in the Cloud Console first + String name = + String.format("projects/%s/locations/%s/processors/%s", projectId, location, processorId); + + // Read the file. + byte[] imageFileData = Files.readAllBytes(Paths.get(filePath)); + + // Convert the image data to a Buffer and base64 encode it. + ByteString content = ByteString.copyFrom(imageFileData); + + RawDocument document = + RawDocument.newBuilder().setContent(content).setMimeType("application/pdf").build(); + + // Configure the process request. + ProcessRequest request = + ProcessRequest.newBuilder().setName(name).setRawDocument(document).build(); + + // Recognizes text entities in the PDF document + ProcessResponse result = client.processDocument(request); + Document documentResponse = result.getDocument(); + + // Get all of the document text as one big string + String text = documentResponse.getText(); + + // Read the text recognition output from the processor + System.out.println("The document contains the following paragraphs:"); + Document.Page firstPage = documentResponse.getPages(0); + List paragraphs = firstPage.getParagraphsList(); + + for (Document.Page.Paragraph paragraph : paragraphs) { + String paragraphText = getText(paragraph.getLayout().getTextAnchor(), text); + System.out.printf("Paragraph text:\n%s\n", paragraphText); + } + } + } + + // Extract shards from the text field + private static String getText(Document.TextAnchor textAnchor, String text) { + if (textAnchor.getTextSegmentsList().size() > 0) { + int startIdx = (int) textAnchor.getTextSegments(0).getStartIndex(); + int endIdx = (int) textAnchor.getTextSegments(0).getEndIndex(); + return text.substring(startIdx, endIdx); + } + return "[NO TEXT]"; + } +} +// [END documentai_quickstart] diff --git a/document-ai/src/main/java/documentai/v1beta3/ProcessFormDocument.java b/document-ai/src/main/java/documentai/v1beta3/ProcessFormDocument.java new file mode 100644 index 00000000000..8a50d8533c6 --- /dev/null +++ b/document-ai/src/main/java/documentai/v1beta3/ProcessFormDocument.java @@ -0,0 +1,149 @@ +/* + * 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 documentai.v1beta3; + +// [START documentai_process_form_document] + +import com.google.cloud.documentai.v1beta3.Document; +import com.google.cloud.documentai.v1beta3.DocumentProcessorServiceClient; +import com.google.cloud.documentai.v1beta3.ProcessRequest; +import com.google.cloud.documentai.v1beta3.ProcessResponse; +import com.google.cloud.documentai.v1beta3.RawDocument; +import com.google.protobuf.ByteString; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +public class ProcessFormDocument { + public static void processFormDocument() + throws IOException, InterruptedException, ExecutionException, TimeoutException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String location = "your-project-location"; // Format is "us" or "eu". + String processerId = "your-processor-id"; + String filePath = "path/to/input/file.pdf"; + processFormDocument(projectId, location, processerId, filePath); + } + + public static void processFormDocument( + String projectId, String location, String processorId, String filePath) + throws IOException, InterruptedException, ExecutionException, 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 (DocumentProcessorServiceClient client = DocumentProcessorServiceClient.create()) { + // The full resource name of the processor, e.g.: + // projects/project-id/locations/location/processor/processor-id + // You must create new processors in the Cloud Console first + String name = + String.format("projects/%s/locations/%s/processors/%s", projectId, location, processorId); + + // Read the file. + byte[] imageFileData = Files.readAllBytes(Paths.get(filePath)); + + // Convert the image data to a Buffer and base64 encode it. + ByteString content = ByteString.copyFrom(imageFileData); + + RawDocument document = + RawDocument.newBuilder().setContent(content).setMimeType("application/pdf").build(); + + // Configure the process request. + ProcessRequest request = + ProcessRequest.newBuilder().setName(name).setRawDocument(document).build(); + + // Recognizes text entities in the PDF document + ProcessResponse result = client.processDocument(request); + Document documentResponse = result.getDocument(); + + System.out.println("Document processing complete."); + + // Read the text recognition output from the processor + // For a full list of Document object attributes, + // please reference this page: + // https://googleapis.dev/java/google-cloud-document-ai/latest/index.html + + // Get all of the document text as one big string + String text = documentResponse.getText(); + System.out.printf("Full document text: '%s'\n", removeNewlines(text)); + + // Read the text recognition output from the processor + List pages = documentResponse.getPagesList(); + System.out.printf("There are %s page(s) in this document.\n", pages.size()); + + for (Document.Page page : pages) { + System.out.printf("\n\n**** Page %d ****\n", page.getPageNumber()); + + List tables = page.getTablesList(); + System.out.printf("Found %d table(s):\n", tables.size()); + for (Document.Page.Table table : tables) { + printTableInfo(table, text); + } + + List formFields = page.getFormFieldsList(); + System.out.printf("Found %d form fields:\n", formFields.size()); + for (Document.Page.FormField formField : formFields) { + String fieldName = getLayoutText(formField.getFieldName().getTextAnchor(), text); + String fieldValue = getLayoutText(formField.getFieldValue().getTextAnchor(), text); + System.out.printf( + " * '%s': '%s'\n", removeNewlines(fieldName), removeNewlines(fieldValue)); + } + } + } + } + + private static void printTableInfo(Document.Page.Table table, String text) { + Document.Page.Table.TableRow firstBodyRow = table.getBodyRows(0); + int columnCount = firstBodyRow.getCellsCount(); + System.out.printf( + " Table with %d columns and %d rows:\n", columnCount, table.getBodyRowsCount()); + + Document.Page.Table.TableRow headerRow = table.getHeaderRows(0); + StringBuilder headerRowText = new StringBuilder(); + for (Document.Page.Table.TableCell cell : headerRow.getCellsList()) { + String columnName = getLayoutText(cell.getLayout().getTextAnchor(), text); + headerRowText.append(String.format("%s | ", removeNewlines(columnName))); + } + headerRowText.setLength(headerRowText.length() - 3); + System.out.printf(" Collumns: %s\n", headerRowText.toString()); + + StringBuilder firstRowText = new StringBuilder(); + for (Document.Page.Table.TableCell cell : firstBodyRow.getCellsList()) { + String cellText = getLayoutText(cell.getLayout().getTextAnchor(), text); + firstRowText.append(String.format("%s | ", removeNewlines(cellText))); + } + firstRowText.setLength(firstRowText.length() - 3); + System.out.printf(" First row data: %s\n", firstRowText.toString()); + } + + // Extract shards from the text field + private static String getLayoutText(Document.TextAnchor textAnchor, String text) { + if (textAnchor.getTextSegmentsList().size() > 0) { + int startIdx = (int) textAnchor.getTextSegments(0).getStartIndex(); + int endIdx = (int) textAnchor.getTextSegments(0).getEndIndex(); + return text.substring(startIdx, endIdx); + } + return "[NO TEXT]"; + } + + private static String removeNewlines(String s) { + return s.replace("\n", "").replace("\r", ""); + } +} +// [END documentai_process_form_document] diff --git a/document-ai/src/main/java/documentai/v1beta3/ProcessOcrDocument.java b/document-ai/src/main/java/documentai/v1beta3/ProcessOcrDocument.java new file mode 100644 index 00000000000..f483929a13e --- /dev/null +++ b/document-ai/src/main/java/documentai/v1beta3/ProcessOcrDocument.java @@ -0,0 +1,172 @@ +/* + * 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 documentai.v1beta3; + +// [START documentai_process_ocr_document] + +import com.google.cloud.documentai.v1beta3.Document; +import com.google.cloud.documentai.v1beta3.DocumentProcessorServiceClient; +import com.google.cloud.documentai.v1beta3.ProcessRequest; +import com.google.cloud.documentai.v1beta3.ProcessResponse; +import com.google.cloud.documentai.v1beta3.RawDocument; +import com.google.protobuf.ByteString; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +public class ProcessOcrDocument { + public static void processOcrDocument() + throws IOException, InterruptedException, ExecutionException, TimeoutException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String location = "your-project-location"; // Format is "us" or "eu". + String processerId = "your-processor-id"; + String filePath = "path/to/input/file.pdf"; + processOcrDocument(projectId, location, processerId, filePath); + } + + public static void processOcrDocument( + String projectId, String location, String processorId, String filePath) + throws IOException, InterruptedException, ExecutionException, 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 (DocumentProcessorServiceClient client = DocumentProcessorServiceClient.create()) { + // The full resource name of the processor, e.g.: + // projects/project-id/locations/location/processor/processor-id + // You must create new processors in the Cloud Console first + String name = + String.format("projects/%s/locations/%s/processors/%s", projectId, location, processorId); + + // Read the file. + byte[] imageFileData = Files.readAllBytes(Paths.get(filePath)); + + // Convert the image data to a Buffer and base64 encode it. + ByteString content = ByteString.copyFrom(imageFileData); + + RawDocument document = + RawDocument.newBuilder().setContent(content).setMimeType("application/pdf").build(); + + // Configure the process request. + ProcessRequest request = + ProcessRequest.newBuilder().setName(name).setRawDocument(document).build(); + + // Recognizes text entities in the PDF document + ProcessResponse result = client.processDocument(request); + Document documentResponse = result.getDocument(); + + System.out.println("Document processing complete."); + + // Read the text recognition output from the processor + // For a full list of Document object attributes, + // please reference this page: + // https://googleapis.dev/java/google-cloud-document-ai/latest/index.html + + // Get all of the document text as one big string + String text = documentResponse.getText(); + System.out.printf("Full document text: '%s'\n", escapeNewlines(text)); + + // Read the text recognition output from the processor + List pages = documentResponse.getPagesList(); + System.out.printf("There are %s page(s) in this document.\n", pages.size()); + + for (Document.Page page : pages) { + System.out.printf("Page %d:\n", page.getPageNumber()); + printPageDimensions(page.getDimension()); + printDetectedLanguages(page.getDetectedLanguagesList()); + printParagraphs(page.getParagraphsList(), text); + printBlocks(page.getBlocksList(), text); + printLines(page.getLinesList(), text); + printTokens(page.getTokensList(), text); + } + } + } + + private static void printPageDimensions(Document.Page.Dimension dimension) { + String unit = dimension.getUnit(); + System.out.printf(" Width: %.1f %s\n", dimension.getWidth(), unit); + System.out.printf(" Height: %.1f %s\n", dimension.getHeight(), unit); + } + + private static void printDetectedLanguages( + List detectedLangauges) { + System.out.println(" Detected languages:"); + for (Document.Page.DetectedLanguage detectedLanguage : detectedLangauges) { + String languageCode = detectedLanguage.getLanguageCode(); + float confidence = detectedLanguage.getConfidence(); + System.out.printf(" %s (%.2f%%)\n", languageCode, confidence * 100.0); + } + } + + private static void printParagraphs(List paragraphs, String text) { + System.out.printf(" %d paragraphs detected:\n", paragraphs.size()); + Document.Page.Paragraph firstParagraph = paragraphs.get(0); + String firstParagraphText = getLayoutText(firstParagraph.getLayout().getTextAnchor(), text); + System.out.printf(" First paragraph text: %s\n", escapeNewlines(firstParagraphText)); + Document.Page.Paragraph lastParagraph = paragraphs.get(paragraphs.size() - 1); + String lastParagraphText = getLayoutText(lastParagraph.getLayout().getTextAnchor(), text); + System.out.printf(" Last paragraph text: %s\n", escapeNewlines(lastParagraphText)); + } + + private static void printBlocks(List blocks, String text) { + System.out.printf(" %d blocks detected:\n", blocks.size()); + Document.Page.Block firstBlock = blocks.get(0); + String firstBlockText = getLayoutText(firstBlock.getLayout().getTextAnchor(), text); + System.out.printf(" First block text: %s\n", escapeNewlines(firstBlockText)); + Document.Page.Block lastBlock = blocks.get(blocks.size() - 1); + String lastBlockText = getLayoutText(lastBlock.getLayout().getTextAnchor(), text); + System.out.printf(" Last block text: %s\n", escapeNewlines(lastBlockText)); + } + + private static void printLines(List lines, String text) { + System.out.printf(" %d lines detected:\n", lines.size()); + Document.Page.Line firstLine = lines.get(0); + String firstLineText = getLayoutText(firstLine.getLayout().getTextAnchor(), text); + System.out.printf(" First line text: %s\n", escapeNewlines(firstLineText)); + Document.Page.Line lastLine = lines.get(lines.size() - 1); + String lastLineText = getLayoutText(lastLine.getLayout().getTextAnchor(), text); + System.out.printf(" Last line text: %s\n", escapeNewlines(lastLineText)); + } + + private static void printTokens(List tokens, String text) { + System.out.printf(" %d tokens detected:\n", tokens.size()); + Document.Page.Token firstToken = tokens.get(0); + String firstTokenText = getLayoutText(firstToken.getLayout().getTextAnchor(), text); + System.out.printf(" First token text: %s\n", escapeNewlines(firstTokenText)); + Document.Page.Token lastToken = tokens.get(tokens.size() - 1); + String lastTokenText = getLayoutText(lastToken.getLayout().getTextAnchor(), text); + System.out.printf(" Last token text: %s\n", escapeNewlines(lastTokenText)); + } + + // Extract shards from the text field + private static String getLayoutText(Document.TextAnchor textAnchor, String text) { + if (textAnchor.getTextSegmentsList().size() > 0) { + int startIdx = (int) textAnchor.getTextSegments(0).getStartIndex(); + int endIdx = (int) textAnchor.getTextSegments(0).getEndIndex(); + return text.substring(startIdx, endIdx); + } + return "[NO TEXT]"; + } + + private static String escapeNewlines(String s) { + return s.replace("\n", "\\n").replace("\r", "\\r"); + } +} +// [END documentai_process_ocr_document] diff --git a/document-ai/src/main/java/documentai/v1beta3/ProcessQualityDocument.java b/document-ai/src/main/java/documentai/v1beta3/ProcessQualityDocument.java new file mode 100644 index 00000000000..3e80a574f72 --- /dev/null +++ b/document-ai/src/main/java/documentai/v1beta3/ProcessQualityDocument.java @@ -0,0 +1,98 @@ +/* + * 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 documentai.v1beta3; + +// [START documentai_process_quality_document] + +import com.google.cloud.documentai.v1beta3.Document; +import com.google.cloud.documentai.v1beta3.DocumentProcessorServiceClient; +import com.google.cloud.documentai.v1beta3.ProcessRequest; +import com.google.cloud.documentai.v1beta3.ProcessResponse; +import com.google.cloud.documentai.v1beta3.RawDocument; +import com.google.protobuf.ByteString; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +public class ProcessQualityDocument { + public static void processQualityDocument() + throws IOException, InterruptedException, ExecutionException, TimeoutException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String location = "your-project-location"; // Format is "us" or "eu". + String processerId = "your-processor-id"; + String filePath = "path/to/input/file.pdf"; + processQualityDocument(projectId, location, processerId, filePath); + } + + public static void processQualityDocument( + String projectId, String location, String processorId, String filePath) + throws IOException, InterruptedException, ExecutionException, 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 (DocumentProcessorServiceClient client = DocumentProcessorServiceClient.create()) { + // The full resource name of the processor, e.g.: + // projects/project-id/locations/location/processor/processor-id + // You must create new processors in the Cloud Console first + String name = + String.format("projects/%s/locations/%s/processors/%s", projectId, location, processorId); + + // Read the file. + byte[] imageFileData = Files.readAllBytes(Paths.get(filePath)); + + // Convert the image data to a Buffer and base64 encode it. + ByteString content = ByteString.copyFrom(imageFileData); + + RawDocument document = + RawDocument.newBuilder().setContent(content).setMimeType("application/pdf").build(); + + // Configure the process request. + ProcessRequest request = + ProcessRequest.newBuilder().setName(name).setRawDocument(document).build(); + + // Recognizes text entities in the PDF document + ProcessResponse result = client.processDocument(request); + Document documentResponse = result.getDocument(); + + System.out.println("Document processing complete."); + + // Read the quality-specific information from the output from the + // Intelligent Document Quality Processor: + // https://cloud.google.com/document-ai/docs/processors-list#processor_doc-quality-processor + // OCR and other data is also present in the quality processor's response. + // Please see the OCR and other samples for how to parse other data in the + // response. + List entities = documentResponse.getEntitiesList(); + for (Document.Entity entity : entities) { + float entityConfidence = entity.getConfidence(); + long pageNumber = entity.getPageAnchor().getPageRefs(0).getPage() + 1; + System.out.printf( + "Page %d has a quality score of (%.2f%%):\n", pageNumber, entityConfidence * 100.0); + for (Document.Entity property : entity.getPropertiesList()) { + float propertyConfidence = property.getConfidence(); + String propertyType = property.getType(); + System.out.printf(" * %s score of %.2f%%\n", propertyType, propertyConfidence * 100.0); + } + } + } + } +} +// [END documentai_process_quality_document] diff --git a/document-ai/src/main/java/documentai/v1beta3/ProcessSpecializedDocument.java b/document-ai/src/main/java/documentai/v1beta3/ProcessSpecializedDocument.java new file mode 100644 index 00000000000..5cbb1af107c --- /dev/null +++ b/document-ai/src/main/java/documentai/v1beta3/ProcessSpecializedDocument.java @@ -0,0 +1,106 @@ +/* + * 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 documentai.v1beta3; + +// [START documentai_process_specialized_document] + +import com.google.cloud.documentai.v1beta3.Document; +import com.google.cloud.documentai.v1beta3.DocumentProcessorServiceClient; +import com.google.cloud.documentai.v1beta3.ProcessRequest; +import com.google.cloud.documentai.v1beta3.ProcessResponse; +import com.google.cloud.documentai.v1beta3.RawDocument; +import com.google.protobuf.ByteString; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +public class ProcessSpecializedDocument { + public static void processSpecializedDocument() + throws IOException, InterruptedException, ExecutionException, TimeoutException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String location = "your-project-location"; // Format is "us" or "eu". + String processerId = "your-processor-id"; + String filePath = "path/to/input/file.pdf"; + processSpecializedDocument(projectId, location, processerId, filePath); + } + + public static void processSpecializedDocument( + String projectId, String location, String processorId, String filePath) + throws IOException, InterruptedException, ExecutionException, 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 (DocumentProcessorServiceClient client = DocumentProcessorServiceClient.create()) { + // The full resource name of the processor, e.g.: + // projects/project-id/locations/location/processor/processor-id + // You must create new processors in the Cloud Console first + String name = + String.format("projects/%s/locations/%s/processors/%s", projectId, location, processorId); + + // Read the file. + byte[] imageFileData = Files.readAllBytes(Paths.get(filePath)); + + // Convert the image data to a Buffer and base64 encode it. + ByteString content = ByteString.copyFrom(imageFileData); + + RawDocument document = + RawDocument.newBuilder().setContent(content).setMimeType("application/pdf").build(); + + // Configure the process request. + ProcessRequest request = + ProcessRequest.newBuilder().setName(name).setRawDocument(document).build(); + + // Recognizes text entities in the PDF document + ProcessResponse result = client.processDocument(request); + Document documentResponse = result.getDocument(); + + System.out.println("Document processing complete."); + + // Read fields specificly from the specalized US drivers license processor: + // https://cloud.google.com/document-ai/docs/processors-list#processor_us-driver-license-parser + // retriving data from other specalized processors follow a similar pattern. + // For a complete list of processors see: + // https://cloud.google.com/document-ai/docs/processors-list + // + // OCR and other data is also present in the quality processor's response. + // Please see the OCR and other samples for how to parse other data in the + // response. + for (Document.Entity entity : documentResponse.getEntitiesList()) { + // Fields detected. For a full list of fields for each processor see + // the processor documentation: + // https://cloud.google.com/document-ai/docs/processors-list + String entityType = entity.getType(); + // some other value formats in addition to text are availible + // e.g. dates: `entity.getNormalizedValue().getDateValue().getYear()` + // check for normilized value with `entity.hasNormalizedValue()` + String entityTextValue = escapeNewlines(entity.getTextAnchor().getContent()); + float entityConfidence = entity.getConfidence(); + System.out.printf( + " * %s: %s (%.2f%% confident)\n", + entityType, entityTextValue, entityConfidence * 100.0); + } + } + } + + private static String escapeNewlines(String s) { + return s.replace("\n", "\\n").replace("\r", "\\r"); + } +} +// [END documentai_process_specialized_document] diff --git a/document-ai/src/main/java/documentai/v1beta3/ProcessSplitterDocument.java b/document-ai/src/main/java/documentai/v1beta3/ProcessSplitterDocument.java new file mode 100644 index 00000000000..e63e2f8e4cf --- /dev/null +++ b/document-ai/src/main/java/documentai/v1beta3/ProcessSplitterDocument.java @@ -0,0 +1,112 @@ +/* + * 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 documentai.v1beta3; + +// [START documentai_process_splitter_document] + +import com.google.cloud.documentai.v1beta3.Document; +import com.google.cloud.documentai.v1beta3.DocumentProcessorServiceClient; +import com.google.cloud.documentai.v1beta3.ProcessRequest; +import com.google.cloud.documentai.v1beta3.ProcessResponse; +import com.google.cloud.documentai.v1beta3.RawDocument; +import com.google.protobuf.ByteString; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +public class ProcessSplitterDocument { + public static void processSplitterDocument() + throws IOException, InterruptedException, ExecutionException, TimeoutException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String location = "your-project-location"; // Format is "us" or "eu". + String processerId = "your-processor-id"; + String filePath = "path/to/input/file.pdf"; + processSplitterDocument(projectId, location, processerId, filePath); + } + + public static void processSplitterDocument( + String projectId, String location, String processorId, String filePath) + throws IOException, InterruptedException, ExecutionException, 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 (DocumentProcessorServiceClient client = DocumentProcessorServiceClient.create()) { + // The full resource name of the processor, e.g.: + // projects/project-id/locations/location/processor/processor-id + // You must create new processors in the Cloud Console first + String name = + String.format("projects/%s/locations/%s/processors/%s", projectId, location, processorId); + + // Read the file. + byte[] imageFileData = Files.readAllBytes(Paths.get(filePath)); + + // Convert the image data to a Buffer and base64 encode it. + ByteString content = ByteString.copyFrom(imageFileData); + + RawDocument document = + RawDocument.newBuilder().setContent(content).setMimeType("application/pdf").build(); + + // Configure the process request. + ProcessRequest request = + ProcessRequest.newBuilder().setName(name).setRawDocument(document).build(); + + // Recognizes text entities in the PDF document + ProcessResponse result = client.processDocument(request); + Document documentResponse = result.getDocument(); + + System.out.println("Document processing complete."); + + // Read the splitter output from the document splitter processor: + // https://cloud.google.com/document-ai/docs/processors-list#processor_doc-splitter + // This processor only provides text for the document and information on how + // to split the document on logical boundaries. To identify and extract text, + // form elements, and entities please see other processors like the OCR, form, + // and specalized processors. + List entities = documentResponse.getEntitiesList(); + System.out.printf("Found %d subdocuments:\n", entities.size()); + for (Document.Entity entity : entities) { + float entityConfidence = entity.getConfidence(); + String pagesRangeText = pageRefsToString(entity.getPageAnchor().getPageRefsList()); + String subdocumentType = entity.getType(); + if (subdocumentType.isEmpty()) { + System.out.printf( + "%.2f%% confident that %s a subdocument.\n", entityConfidence * 100, pagesRangeText); + } else { + System.out.printf( + "%.2f%% confident that %s a '%s' subdocument.\n", + entityConfidence * 100, pagesRangeText, subdocumentType); + } + } + } + } + + // Converts page reference(s) to a string describing the page or page range. + private static String pageRefsToString(List pageRefs) { + if (pageRefs.size() == 1) { + return String.format("page %d is", pageRefs.get(0).getPage() + 1); + } else { + long start = pageRefs.get(0).getPage() + 1; + long end = pageRefs.get(1).getPage() + 1; + return String.format("pages %d to %d are", start, end); + } + } +} +// [END documentai_process_splitter_document] diff --git a/document-ai/src/test/java/documentai/v1/BatchProcessDocumentTest.java b/document-ai/src/test/java/documentai/v1/BatchProcessDocumentTest.java new file mode 100644 index 00000000000..1024ae71fc3 --- /dev/null +++ b/document-ai/src/test/java/documentai/v1/BatchProcessDocumentTest.java @@ -0,0 +1,116 @@ +/* + * 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 documentai.v1; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertNotNull; + +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 java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +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.Test; + +public class BatchProcessDocumentTest { + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String PROCESSOR_ID = "88541adc6eeec481"; + private static final String BUCKET_NAME = + String.format("document-ai-output-test-%s", UUID.randomUUID()); + private static final String INPUT_URI = "gs://cloud-samples-data/documentai/invoice.pdf"; + private static final String OUTPUT_PREFIX = String.format("%s", UUID.randomUUID()); + private static final String OUTPUT_BUCKET_NAME = PROJECT_ID; + + private ByteArrayOutputStream bout; + private PrintStream out; + private PrintStream originalPrintStream; + + private static void requireEnvVar(String varName) { + assertNotNull( + String.format("Environment variable '%s' must be set to perform these tests.", varName), + System.getenv(varName)); + } + + private static void cleanUpBucket() { + Storage storage = StorageOptions.getDefaultInstance().getService(); + Page blobs = + storage.list( + BUCKET_NAME, + Storage.BlobListOption.currentDirectory(), + Storage.BlobListOption.prefix(OUTPUT_PREFIX)); + + deleteDirectory(storage, blobs); + } + + private static void deleteDirectory(Storage storage, Page blobs) { + for (Blob blob : blobs.iterateAll()) { + if (!blob.delete()) { + Page subBlobs = + storage.list( + BUCKET_NAME, + Storage.BlobListOption.currentDirectory(), + Storage.BlobListOption.prefix(blob.getName())); + + deleteDirectory(storage, subBlobs); + } + } + } + + @Before + public void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + + Storage storage = StorageOptions.getDefaultInstance().getService(); + storage.create(BucketInfo.of(BUCKET_NAME)); + } + + @Test + public void testBatchProcessDocument() + throws InterruptedException, ExecutionException, TimeoutException, IOException { + // parse the GCS invoice as a form. + BatchProcessDocument.batchProcessDocument( + PROJECT_ID, "us", PROCESSOR_ID, INPUT_URI, OUTPUT_BUCKET_NAME, OUTPUT_PREFIX); + String got = bout.toString(); + + assertThat(got).contains("Paragraph text:"); + assertThat(got).contains("Extracted"); + } + + @After + public void tearDown() { + cleanUpBucket(); + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/document-ai/src/test/java/documentai/v1/ProcessDocumentTest.java b/document-ai/src/test/java/documentai/v1/ProcessDocumentTest.java new file mode 100644 index 00000000000..6a4a35aa9eb --- /dev/null +++ b/document-ai/src/test/java/documentai/v1/ProcessDocumentTest.java @@ -0,0 +1,76 @@ +/* + * 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 documentai.v1; + +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.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class ProcessDocumentTest { + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String PROCESSOR_ID = "88541adc6eeec481"; + private static final String FILE_PATH = "resources/invoice.pdf"; + + private ByteArrayOutputStream bout; + private PrintStream out; + private PrintStream originalPrintStream; + + private static void requireEnvVar(String varName) { + assertNotNull( + String.format("Environment variable '%s' must be set to perform these tests.", varName), + System.getenv(varName)); + } + + @Before + public void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + } + + @Test + public void testProcessDocument() + throws InterruptedException, ExecutionException, IOException, TimeoutException { + // parse the GCS invoice as a form. + ProcessDocument.processDocument(PROJECT_ID, "us", PROCESSOR_ID, FILE_PATH); + String got = bout.toString(); + + assertThat(got).contains("Paragraph text:"); + assertThat(got).contains("Extracted"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/document-ai/src/test/java/documentai/v1/QuickStartTest.java b/document-ai/src/test/java/documentai/v1/QuickStartTest.java new file mode 100644 index 00000000000..afaa4b1c7f1 --- /dev/null +++ b/document-ai/src/test/java/documentai/v1/QuickStartTest.java @@ -0,0 +1,75 @@ +/* + * 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 documentai.v1; + +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.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class QuickStartTest { + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String PROCESSOR_ID = "88541adc6eeec481"; + private static final String FILE_PATH = "resources/invoice.pdf"; + + private ByteArrayOutputStream bout; + private PrintStream out; + private PrintStream originalPrintStream; + + private static void requireEnvVar(String varName) { + assertNotNull( + String.format("Environment variable '%s' must be set to perform these tests.", varName), + System.getenv(varName)); + } + + @Before + public void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + } + + @Test + public void testQuickStart() + throws InterruptedException, ExecutionException, IOException, TimeoutException { + // parse the GCS invoice as a form. + QuickStart.quickStart(PROJECT_ID, "us", PROCESSOR_ID, FILE_PATH); + String got = bout.toString(); + + assertThat(got).contains("Paragraph text:"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/document-ai/src/test/java/documentai/v1beta3/ProcessFormDocumentTest.java b/document-ai/src/test/java/documentai/v1beta3/ProcessFormDocumentTest.java new file mode 100644 index 00000000000..7491d7442e7 --- /dev/null +++ b/document-ai/src/test/java/documentai/v1beta3/ProcessFormDocumentTest.java @@ -0,0 +1,78 @@ +/* + * 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 documentai.v1beta3; + +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.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class ProcessFormDocumentTest { + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String PROCESSOR_ID = "88541adc6eeec481"; + private static final String FILE_PATH = "resources/invoice.pdf"; + + private ByteArrayOutputStream bout; + private PrintStream out; + private PrintStream originalPrintStream; + + private static void requireEnvVar(String varName) { + assertNotNull( + String.format("Environment variable '%s' must be set to perform these tests.", varName), + System.getenv(varName)); + } + + @Before + public void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + } + + @Test + public void testProcessFormDocument() + throws InterruptedException, ExecutionException, IOException, TimeoutException { + // parse the GCS invoice as a form. + ProcessFormDocument.processFormDocument(PROJECT_ID, "us", PROCESSOR_ID, FILE_PATH); + String got = bout.toString(); + + assertThat(got).contains("There are 1 page(s) in this document."); + assertThat(got).contains("Table with 4 columns and 6 rows"); + assertThat(got).contains("Found 13 form fields"); + assertThat(got).contains("'BALANCE DUE': '$2140.00'"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/document-ai/src/test/java/documentai/v1beta3/ProcessOcrDocumentTest.java b/document-ai/src/test/java/documentai/v1beta3/ProcessOcrDocumentTest.java new file mode 100644 index 00000000000..0c2da47156b --- /dev/null +++ b/document-ai/src/test/java/documentai/v1beta3/ProcessOcrDocumentTest.java @@ -0,0 +1,77 @@ +/* + * 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 documentai.v1beta3; + +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.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class ProcessOcrDocumentTest { + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String PROCESSOR_ID = "f9018d35bc5edc1e"; + private static final String FILE_PATH = "resources/handwritten_form.pdf"; + + private ByteArrayOutputStream bout; + private PrintStream out; + private PrintStream originalPrintStream; + + private static void requireEnvVar(String varName) { + assertNotNull( + String.format("Environment variable '%s' must be set to perform these tests.", varName), + System.getenv(varName)); + } + + @Before + public void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + } + + @Test + public void testProcessOcrDocument() + throws InterruptedException, ExecutionException, IOException, TimeoutException { + // parse the GCS invoice as a form. + ProcessOcrDocument.processOcrDocument(PROJECT_ID, "us", PROCESSOR_ID, FILE_PATH); + String got = bout.toString(); + + assertThat(got).contains("Page 1"); + assertThat(got).contains("en"); + assertThat(got).contains("FakeDoc"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/document-ai/src/test/java/documentai/v1beta3/ProcessQualityDocumentTest.java b/document-ai/src/test/java/documentai/v1beta3/ProcessQualityDocumentTest.java new file mode 100644 index 00000000000..7379dbf0f30 --- /dev/null +++ b/document-ai/src/test/java/documentai/v1beta3/ProcessQualityDocumentTest.java @@ -0,0 +1,77 @@ +/* + * 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 documentai.v1beta3; + +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.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class ProcessQualityDocumentTest { + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String PROCESSOR_ID = "f80f55e03d4c20ed"; + private static final String FILE_PATH = "resources/document_quality_poor.pdf"; + + private ByteArrayOutputStream bout; + private PrintStream out; + private PrintStream originalPrintStream; + + private static void requireEnvVar(String varName) { + assertNotNull( + String.format("Environment variable '%s' must be set to perform these tests.", varName), + System.getenv(varName)); + } + + @Before + public void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + } + + @Test + public void testProcessQualityDocument() + throws InterruptedException, ExecutionException, IOException, TimeoutException { + // parse the GCS invoice as a form. + ProcessQualityDocument.processQualityDocument(PROJECT_ID, "us", PROCESSOR_ID, FILE_PATH); + String got = bout.toString(); + + assertThat(got).contains("Page 1 has a quality score of"); + assertThat(got).contains("defect_blurry score of 9"); + assertThat(got).contains("defect_noisy"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/document-ai/src/test/java/documentai/v1beta3/ProcessSpecializedDocumentTest.java b/document-ai/src/test/java/documentai/v1beta3/ProcessSpecializedDocumentTest.java new file mode 100644 index 00000000000..5f5b21d078d --- /dev/null +++ b/document-ai/src/test/java/documentai/v1beta3/ProcessSpecializedDocumentTest.java @@ -0,0 +1,77 @@ +/* + * 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 documentai.v1beta3; + +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.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class ProcessSpecializedDocumentTest { + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String PROCESSOR_ID = "ae8bc99f01b36b5e"; + private static final String FILE_PATH = "resources/us_driver_license.pdf"; + + private ByteArrayOutputStream bout; + private PrintStream out; + private PrintStream originalPrintStream; + + private static void requireEnvVar(String varName) { + assertNotNull( + String.format("Environment variable '%s' must be set to perform these tests.", varName), + System.getenv(varName)); + } + + @Before + public void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + } + + @Test + public void testProcessSpecializedDocument() + throws InterruptedException, ExecutionException, IOException, TimeoutException { + // parse the GCS invoice as a form. + ProcessSpecializedDocument.processSpecializedDocument( + PROJECT_ID, "us", PROCESSOR_ID, FILE_PATH); + String got = bout.toString(); + + assertThat(got).contains("Document Id"); + assertThat(got).contains("97551579"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/document-ai/src/test/java/documentai/v1beta3/ProcessSplitterDocumentTest.java b/document-ai/src/test/java/documentai/v1beta3/ProcessSplitterDocumentTest.java new file mode 100644 index 00000000000..8fcf7aafb6f --- /dev/null +++ b/document-ai/src/test/java/documentai/v1beta3/ProcessSplitterDocumentTest.java @@ -0,0 +1,77 @@ +/* + * 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 documentai.v1beta3; + +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.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class ProcessSplitterDocumentTest { + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String PROCESSOR_ID = "7cb010d65184a4d"; + private static final String FILE_PATH = "resources/multi_document.pdf"; + + private ByteArrayOutputStream bout; + private PrintStream out; + private PrintStream originalPrintStream; + + private static void requireEnvVar(String varName) { + assertNotNull( + String.format("Environment variable '%s' must be set to perform these tests.", varName), + System.getenv(varName)); + } + + @Before + public void checkRequirements() { + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + } + + @Test + public void testProcessSplitterDocument() + throws InterruptedException, ExecutionException, IOException, TimeoutException { + // parse the GCS invoice as a form. + ProcessSplitterDocument.processSplitterDocument(PROJECT_ID, "us", PROCESSOR_ID, FILE_PATH); + String got = bout.toString(); + + assertThat(got).contains("Found 8 subdocuments"); + assertThat(got).contains("confident that pages 1 to 2 are a subdocument"); + assertThat(got).contains("confident that page 10 is a subdocument"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/flexible/cloudsql/pom.xml b/flexible/cloudsql/pom.xml index 12eb7f0ac52..e28f78cd3fd 100644 --- a/flexible/cloudsql/pom.xml +++ b/flexible/cloudsql/pom.xml @@ -61,12 +61,12 @@ com.google.api-client google-api-client-appengine - 1.34.1 + 2.0.1 com.google.api-client google-api-client-servlet - 1.33.4 + 2.0.1 javax.servlet diff --git a/flexible/postgres/pom.xml b/flexible/postgres/pom.xml index 5d14d78976b..105e8da0219 100644 --- a/flexible/postgres/pom.xml +++ b/flexible/postgres/pom.xml @@ -61,12 +61,12 @@ com.google.api-client google-api-client-appengine - 1.34.1 + 2.0.1 com.google.api-client google-api-client-servlet - 1.33.4 + 2.0.1 javax.servlet diff --git a/iot/api-client/end-to-end-example/pom.xml b/iot/api-client/end-to-end-example/pom.xml index 149fd546b79..f98f62d21eb 100644 --- a/iot/api-client/end-to-end-example/pom.xml +++ b/iot/api-client/end-to-end-example/pom.xml @@ -88,7 +88,7 @@ com.google.api-client google-api-client-jackson2 - 1.35.2 + 2.0.1 com.google.http-client diff --git a/iot/api-client/manager/pom.xml b/iot/api-client/manager/pom.xml index 43f5ce03fb8..ba8c65edb13 100644 --- a/iot/api-client/manager/pom.xml +++ b/iot/api-client/manager/pom.xml @@ -97,7 +97,7 @@ com.google.api-client google-api-client-jackson2 - 2.0.0 + 2.0.1 com.google.http-client diff --git a/jobs/v3/src/main/java/com/google/samples/GeneralSearchSample.java b/jobs/v3/src/main/java/com/google/samples/GeneralSearchSample.java index cd006097f85..7c2ee11384e 100644 --- a/jobs/v3/src/main/java/com/google/samples/GeneralSearchSample.java +++ b/jobs/v3/src/main/java/com/google/samples/GeneralSearchSample.java @@ -58,6 +58,7 @@ public final class GeneralSearchSample { private static CloudTalentSolution talentSolutionClient = JobServiceQuickstart.getTalentSolutionClient(); + // [START job_discovery_basic_keyword_search] // [START basic_keyword_search] /** Simple search jobs with keyword. */ @@ -95,7 +96,9 @@ public static void basicSearcJobs(String companyName, String query) System.out.printf("Simple search jobs results: %s\n", searchJobsResponse); } // [END basic_keyword_search] - + // [END job_discovery_basic_keyword_search] + + // [START job_discovery_category_filter_search] // [START category_filter] /** Search on category filter. */ @@ -133,7 +136,9 @@ public static void categoryFilterSearch(String companyName, List categor System.out.printf("Category search jobs results: %s\n", searchJobsResponse); } // [END category_filter] + // [END job_discovery_category_filter_search] + // [START job_discovery_employment_types_filter_search] // [START employment_types_filter] /** Search on employment types. */ @@ -171,7 +176,9 @@ public static void employmentTypesSearch(String companyName, List employ System.out.printf("Employee type search jobs results: %s\n", searchJobsResponse); } // [END employment_types_filter] + // [END job_discovery_employment_types_filter_search] + // [START job_discovery_date_range_filter_search] // [START date_range_filter] /** @@ -218,7 +225,9 @@ public static void dateRangeSearch(String companyName, String startTime, String System.out.printf("Search results on jobs with a date range: %s\n", searchJobsResponse); } // [END date_range_filter] + // [END job_discovery_date_range_filter_search] + // [START job_discovery_language_code_filter_search] // [START language_code_filter] /** Search on language codes. */ @@ -256,7 +265,9 @@ public static void languageCodeSearch(String companyName, List languageC System.out.printf("Search results on jobs with a language code: %s\n", searchJobsResponse); } // [END language_code_filter] + // [END job_discovery_language_code_filter_search] + // [START job_discovery_company_display_name_search] // [START company_display_name_filter] /** Search on company display name. */ @@ -294,7 +305,9 @@ public static void companyDisplayNameSearch(String companyName, List com System.out.printf("Search results by display name of company: %s\n", searchJobsResponse); } // [END company_display_name_filter] - + // [END job_discovery_company_display_name_search] + + // [START job_discovery_compensation_search] // [START compensation_filter] /** Search on compensation. */ @@ -346,6 +359,7 @@ public static void compensationSearch(String companyName) System.out.printf("Search results by compensation: %s\n", searchJobsResponse); } // [END compensation_filter] + // [END job_discovery_compensation_search] public static void main(String... args) throws Exception { Company companyToBeCreated = BasicCompanySample.generateCompany().setDisplayName("Google"); diff --git a/kms/pom.xml b/kms/pom.xml index 805ed0a3f9a..b563cd40e22 100644 --- a/kms/pom.xml +++ b/kms/pom.xml @@ -1,9 +1,11 @@ - + + 4.0.0 - kms - kms-samples + com.example.kms + cloudkms-snippets jar + Google Cloud Key Management Service Snippets + https://github.com/GoogleCloudPlatform/java-docs-samples/tree/main/kms + + + + com.google.cloud + libraries-bom + 26.1.4 + pom + import + + + + com.google.cloud google-cloud-kms - 2.4.4 - + com.google.protobuf protobuf-java-util - 3.20.1 - - - com.google.protobuf - protobuf-java - 3.20.3 - - - - com.google.guava - guava - 31.1-jre - - - junit junit @@ -59,5 +59,7 @@ 1.1.3 test + + diff --git a/kms/src/main/java/kms/CreateKeyAsymmetricDecrypt.java b/kms/src/main/java/kms/CreateKeyAsymmetricDecrypt.java index 2eccd6e38ca..5e4fafee85d 100644 --- a/kms/src/main/java/kms/CreateKeyAsymmetricDecrypt.java +++ b/kms/src/main/java/kms/CreateKeyAsymmetricDecrypt.java @@ -23,6 +23,7 @@ import com.google.cloud.kms.v1.CryptoKeyVersionTemplate; import com.google.cloud.kms.v1.KeyManagementServiceClient; import com.google.cloud.kms.v1.KeyRingName; +import com.google.protobuf.Duration; import java.io.IOException; public class CreateKeyAsymmetricDecrypt { @@ -55,6 +56,9 @@ public void createKeyAsymmetricDecrypt( .setVersionTemplate( CryptoKeyVersionTemplate.newBuilder() .setAlgorithm(CryptoKeyVersionAlgorithm.RSA_DECRYPT_OAEP_2048_SHA256)) + + // Optional: customize how long key versions should be kept before destroying. + .setDestroyScheduledDuration(Duration.newBuilder().setSeconds(24 * 60 * 60)) .build(); // Create the key. diff --git a/kms/src/main/java/kms/CreateKeyAsymmetricSign.java b/kms/src/main/java/kms/CreateKeyAsymmetricSign.java index b3c4ca1d177..d5d1b9b47d3 100644 --- a/kms/src/main/java/kms/CreateKeyAsymmetricSign.java +++ b/kms/src/main/java/kms/CreateKeyAsymmetricSign.java @@ -23,6 +23,7 @@ import com.google.cloud.kms.v1.CryptoKeyVersionTemplate; import com.google.cloud.kms.v1.KeyManagementServiceClient; import com.google.cloud.kms.v1.KeyRingName; +import com.google.protobuf.Duration; import java.io.IOException; public class CreateKeyAsymmetricSign { @@ -54,6 +55,9 @@ public void createKeyAsymmetricSign( .setVersionTemplate( CryptoKeyVersionTemplate.newBuilder() .setAlgorithm(CryptoKeyVersionAlgorithm.RSA_SIGN_PKCS1_2048_SHA256)) + + // Optional: customize how long key versions should be kept before destroying. + .setDestroyScheduledDuration(Duration.newBuilder().setSeconds(24 * 60 * 60)) .build(); // Create the key. diff --git a/kms/src/main/java/kms/CreateKeyHsm.java b/kms/src/main/java/kms/CreateKeyHsm.java index ce6a6e6d514..cc5b8dfd646 100644 --- a/kms/src/main/java/kms/CreateKeyHsm.java +++ b/kms/src/main/java/kms/CreateKeyHsm.java @@ -24,6 +24,7 @@ import com.google.cloud.kms.v1.KeyManagementServiceClient; import com.google.cloud.kms.v1.KeyRingName; import com.google.cloud.kms.v1.ProtectionLevel; +import com.google.protobuf.Duration; import java.io.IOException; public class CreateKeyHsm { @@ -56,6 +57,9 @@ public void createKeyHsm(String projectId, String locationId, String keyRingId, CryptoKeyVersionTemplate.newBuilder() .setProtectionLevel(ProtectionLevel.HSM) .setAlgorithm(CryptoKeyVersionAlgorithm.GOOGLE_SYMMETRIC_ENCRYPTION)) + + // Optional: customize how long key versions should be kept before destroying. + .setDestroyScheduledDuration(Duration.newBuilder().setSeconds(24 * 60 * 60)) .build(); // Create the key. diff --git a/kms/src/main/java/kms/CreateKeyMac.java b/kms/src/main/java/kms/CreateKeyMac.java new file mode 100644 index 00000000000..efc59329354 --- /dev/null +++ b/kms/src/main/java/kms/CreateKeyMac.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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_create_key_mac] +import com.google.cloud.kms.v1.CryptoKey; +import com.google.cloud.kms.v1.CryptoKey.CryptoKeyPurpose; +import com.google.cloud.kms.v1.CryptoKeyVersion.CryptoKeyVersionAlgorithm; +import com.google.cloud.kms.v1.CryptoKeyVersionTemplate; +import com.google.cloud.kms.v1.KeyManagementServiceClient; +import com.google.cloud.kms.v1.KeyRingName; +import java.io.IOException; + +public class CreateKeyMac { + + public void createKeyMac() 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 id = "my-mac-key"; + createKeyMac(projectId, locationId, keyRingId, id); + } + + // Create a new key for use with MacSign. + public void createKeyMac(String projectId, String locationId, String keyRingId, String id) + 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 parent name from the project, location, and key ring. + KeyRingName keyRingName = KeyRingName.of(projectId, locationId, keyRingId); + + // Build the mac key to create. + CryptoKey key = + CryptoKey.newBuilder() + .setPurpose(CryptoKeyPurpose.MAC) + .setVersionTemplate( + CryptoKeyVersionTemplate.newBuilder() + .setAlgorithm(CryptoKeyVersionAlgorithm.HMAC_SHA256)) + .build(); + + // Create the key. + CryptoKey createdKey = client.createCryptoKey(keyRingName, id, key); + System.out.printf("Created mac key %s%n", createdKey.getName()); + } + } +} +// [END kms_create_key_mac] diff --git a/kms/src/main/java/kms/DecryptAsymmetric.java b/kms/src/main/java/kms/DecryptAsymmetric.java index 9ebb9b788a5..21fb635587f 100644 --- a/kms/src/main/java/kms/DecryptAsymmetric.java +++ b/kms/src/main/java/kms/DecryptAsymmetric.java @@ -17,15 +17,10 @@ package kms; // [START kms_decrypt_asymmetric] -import com.google.cloud.kms.v1.AsymmetricDecryptRequest; import com.google.cloud.kms.v1.AsymmetricDecryptResponse; import com.google.cloud.kms.v1.CryptoKeyVersionName; import com.google.cloud.kms.v1.KeyManagementServiceClient; -import com.google.common.hash.HashCode; -import com.google.common.hash.HashFunction; -import com.google.common.hash.Hashing; import com.google.protobuf.ByteString; -import com.google.protobuf.Int64Value; import java.io.IOException; public class DecryptAsymmetric { @@ -61,41 +56,11 @@ public void decryptAsymmetric( CryptoKeyVersionName keyVersionName = CryptoKeyVersionName.of(projectId, locationId, keyRingId, keyId, keyVersionId); - // Optional, but recommended: compute ciphertext's CRC32C. See helpers below. - long ciphertextCrc32c = getCrc32cAsLong(ciphertext); - // Decrypt the ciphertext. - AsymmetricDecryptRequest request = - AsymmetricDecryptRequest.newBuilder() - .setName(keyVersionName.toString()) - .setCiphertext(ByteString.copyFrom(ciphertext)) - .setCiphertextCrc32C( - Int64Value.newBuilder().setValue(ciphertextCrc32c).build()) - .build(); - AsymmetricDecryptResponse response = client.asymmetricDecrypt(request); - - // Optional, but recommended: perform integrity verification on response. - // For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit: - // https://cloud.google.com/kms/docs/data-integrity-guidelines - if (!response.getVerifiedCiphertextCrc32C()) { - throw new IOException("AsymmetricDecrypt: request to server corrupted"); - } - - if (!crcMatches(response.getPlaintextCrc32C().getValue(), - response.getPlaintext().toByteArray())) { - throw new IOException("AsymmetricDecrypt: response from server corrupted"); - } - + AsymmetricDecryptResponse response = + client.asymmetricDecrypt(keyVersionName, ByteString.copyFrom(ciphertext)); System.out.printf("Plaintext: %s%n", response.getPlaintext().toStringUtf8()); } } - - private long getCrc32cAsLong(byte[] data) { - return Hashing.crc32c().hashBytes(data).padToLong(); - } - - private boolean crcMatches(long expectedCrc, byte[] data) { - return expectedCrc == getCrc32cAsLong(data); - } } // [END kms_decrypt_asymmetric] diff --git a/kms/src/main/java/kms/DecryptSymmetric.java b/kms/src/main/java/kms/DecryptSymmetric.java index 5423704fd67..f22bf5fa559 100644 --- a/kms/src/main/java/kms/DecryptSymmetric.java +++ b/kms/src/main/java/kms/DecryptSymmetric.java @@ -18,18 +18,11 @@ // [START kms_decrypt_symmetric] import com.google.cloud.kms.v1.CryptoKeyName; -import com.google.cloud.kms.v1.DecryptRequest; import com.google.cloud.kms.v1.DecryptResponse; import com.google.cloud.kms.v1.KeyManagementServiceClient; -import com.google.common.hash.HashCode; -import com.google.common.hash.HashFunction; -import com.google.common.hash.Hashing; import com.google.protobuf.ByteString; -import com.google.protobuf.Int64Value; import java.io.IOException; - - public class DecryptSymmetric { public void decryptSymmetric() throws IOException { @@ -51,40 +44,14 @@ public void decryptSymmetric( // 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 from the project, location, key ring, and key. + // Build the key version name from the project, location, key ring, and + // key. CryptoKeyName keyName = CryptoKeyName.of(projectId, locationId, keyRingId, keyId); - // Optional, but recommended: compute ciphertext's CRC32C. See helpers below. - long ciphertextCrc32c = getCrc32cAsLong(ciphertext); - - // Decrypt the ciphertext. - DecryptRequest request = - DecryptRequest.newBuilder() - .setName(keyName.toString()) - .setCiphertext(ByteString.copyFrom(ciphertext)) - .setCiphertextCrc32C( - Int64Value.newBuilder().setValue(ciphertextCrc32c).build()) - .build(); - DecryptResponse response = client.decrypt(request); - - // Optional, but recommended: perform integrity verification on response. - // For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit: - // https://cloud.google.com/kms/docs/data-integrity-guidelines - if (!crcMatches(response.getPlaintextCrc32C().getValue(), - response.getPlaintext().toByteArray())) { - throw new IOException("Decrypt: response from server corrupted"); - } - + // Decrypt the response. + DecryptResponse response = client.decrypt(keyName, ByteString.copyFrom(ciphertext)); System.out.printf("Plaintext: %s%n", response.getPlaintext().toStringUtf8()); } } - - private long getCrc32cAsLong(byte[] data) { - return Hashing.crc32c().hashBytes(data).padToLong(); - } - - private boolean crcMatches(long expectedCrc, byte[] data) { - return expectedCrc == getCrc32cAsLong(data); - } } // [END kms_decrypt_symmetric] diff --git a/kms/src/main/java/kms/DisableKeyVersion.java b/kms/src/main/java/kms/DisableKeyVersion.java index ded7fadc6ef..09966b2ed89 100644 --- a/kms/src/main/java/kms/DisableKeyVersion.java +++ b/kms/src/main/java/kms/DisableKeyVersion.java @@ -61,7 +61,7 @@ public void disableKeyVersion( // Create a field mask of updated values. FieldMask fieldMask = FieldMaskUtil.fromString("state"); - // Destroy the key version. + // Disable the key version. CryptoKeyVersion response = client.updateCryptoKeyVersion(keyVersion, fieldMask); System.out.printf("Disabled key version: %s%n", response.getName()); } diff --git a/kms/src/main/java/kms/EnableKeyVersion.java b/kms/src/main/java/kms/EnableKeyVersion.java index 9dcbb846c68..16d13967166 100644 --- a/kms/src/main/java/kms/EnableKeyVersion.java +++ b/kms/src/main/java/kms/EnableKeyVersion.java @@ -61,7 +61,7 @@ public void enableKeyVersion( // Create a field mask of updated values. FieldMask fieldMask = FieldMaskUtil.fromString("state"); - // Destroy the key version. + // Enable the key version. CryptoKeyVersion response = client.updateCryptoKeyVersion(keyVersion, fieldMask); System.out.printf("Enabled key version: %s%n", response.getName()); } diff --git a/kms/src/main/java/kms/EncryptSymmetric.java b/kms/src/main/java/kms/EncryptSymmetric.java index f3cd62ce2a9..cc9080aeb7b 100644 --- a/kms/src/main/java/kms/EncryptSymmetric.java +++ b/kms/src/main/java/kms/EncryptSymmetric.java @@ -18,18 +18,11 @@ // [START kms_encrypt_symmetric] import com.google.cloud.kms.v1.CryptoKeyName; -import com.google.cloud.kms.v1.EncryptRequest; import com.google.cloud.kms.v1.EncryptResponse; import com.google.cloud.kms.v1.KeyManagementServiceClient; -import com.google.common.hash.HashCode; -import com.google.common.hash.HashFunction; -import com.google.common.hash.Hashing; import com.google.protobuf.ByteString; -import com.google.protobuf.Int64Value; import java.io.IOException; - - public class EncryptSymmetric { public void encryptSymmetric() throws IOException { @@ -46,53 +39,19 @@ public void encryptSymmetric() throws IOException { public void encryptSymmetric( String projectId, String locationId, String keyRingId, String keyId, String plaintext) 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 cryptoKeyName = CryptoKeyName.of(projectId, locationId, keyRingId, keyId); - - // Convert plaintext to ByteString. - ByteString plaintextByteString = ByteString.copyFromUtf8(plaintext); - - // Optional, but recommended: compute plaintext's CRC32C. See helper below. - long plaintextCrc32c = getCrc32cAsLong(plaintextByteString.toByteArray()); + // Build the key version name from the project, location, key ring, key, + // and key version. + CryptoKeyName keyVersionName = CryptoKeyName.of(projectId, locationId, keyRingId, keyId); // Encrypt the plaintext. - EncryptRequest request = EncryptRequest.newBuilder() - .setName(cryptoKeyName.toString()) - .setPlaintext(plaintextByteString) - .setPlaintextCrc32C( - Int64Value.newBuilder().setValue(plaintextCrc32c).build()) - .build(); - EncryptResponse response = client.encrypt(request); - - // Optional, but recommended: perform integrity verification on response. - // For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit: - // https://cloud.google.com/kms/docs/data-integrity-guidelines - if (!response.getVerifiedPlaintextCrc32C()) { - throw new IOException("Encrypt: request to server corrupted"); - } - - // See helper below. - if (!crcMatches(response.getCiphertextCrc32C().getValue(), - response.getCiphertext().toByteArray())) { - throw new IOException("Encrypt: response from server corrupted"); - } - + EncryptResponse response = client.encrypt(keyVersionName, ByteString.copyFromUtf8(plaintext)); System.out.printf("Ciphertext: %s%n", response.getCiphertext().toStringUtf8()); } } - - private long getCrc32cAsLong(byte[] data) { - return Hashing.crc32c().hashBytes(data).padToLong(); - } - - private boolean crcMatches(long expectedCrc, byte[] data) { - return expectedCrc == getCrc32cAsLong(data); - } } // [END kms_encrypt_symmetric] diff --git a/kms/src/main/java/kms/GenerateRandomBytes.java b/kms/src/main/java/kms/GenerateRandomBytes.java new file mode 100644 index 00000000000..2d1e4a1d565 --- /dev/null +++ b/kms/src/main/java/kms/GenerateRandomBytes.java @@ -0,0 +1,60 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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_generate_random_bytes] +import com.google.cloud.kms.v1.GenerateRandomBytesResponse; +import com.google.cloud.kms.v1.KeyManagementServiceClient; +import com.google.cloud.kms.v1.LocationName; +import com.google.cloud.kms.v1.ProtectionLevel; +import java.io.IOException; +import java.util.Base64; + +public class GenerateRandomBytes { + + public void generateRandomBytes() throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "your-project-id"; + String locationId = "us-east1"; + int numBytes = 256; + generateRandomBytes(projectId, locationId, numBytes); + } + + // Create a new key for use with MacSign. + public void generateRandomBytes(String projectId, String locationId, int numBytes) + 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 parent name for the location. + LocationName locationName = LocationName.of(projectId, locationId); + + // Generate the bytes. + GenerateRandomBytesResponse response = + client.generateRandomBytes(locationName.toString(), numBytes, ProtectionLevel.HSM); + + // The data comes back as raw bytes, which may include non-printable + // characters. This base64-encodes the result so it can be printed below. + String encodedData = Base64.getEncoder().encodeToString(response.getData().toByteArray()); + + System.out.printf("Random bytes: %s", encodedData); + } + } +} +// [END kms_generate_random_bytes] diff --git a/kms/src/main/java/kms/GetPublicKey.java b/kms/src/main/java/kms/GetPublicKey.java index 3cd112b8242..ba59320f4da 100644 --- a/kms/src/main/java/kms/GetPublicKey.java +++ b/kms/src/main/java/kms/GetPublicKey.java @@ -20,10 +20,6 @@ import com.google.cloud.kms.v1.CryptoKeyVersionName; import com.google.cloud.kms.v1.KeyManagementServiceClient; import com.google.cloud.kms.v1.PublicKey; -import com.google.common.hash.HashCode; -import com.google.common.hash.HashFunction; -import com.google.common.hash.Hashing; -import com.google.protobuf.Int64Value; import java.io.IOException; import java.security.GeneralSecurityException; @@ -55,30 +51,8 @@ public void getPublicKey( // Get the public key. PublicKey publicKey = client.getPublicKey(keyVersionName); - - // Optional, but recommended: perform integrity verification on response. - // For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit: - // https://cloud.google.com/kms/docs/data-integrity-guidelines - if (!publicKey.getName().equals(keyVersionName.toString())) { - throw new IOException("GetPublicKey: request to server corrupted"); - } - - // See helper below. - if (!crcMatches(publicKey.getPemCrc32C().getValue(), - publicKey.getPemBytes().toByteArray())) { - throw new IOException("GetPublicKey: response from server corrupted"); - } - System.out.printf("Public key: %s%n", publicKey.getPem()); } } - - private long getCrc32cAsLong(byte[] data) { - return Hashing.crc32c().hashBytes(data).padToLong(); - } - - private boolean crcMatches(long expectedCrc, byte[] data) { - return expectedCrc == getCrc32cAsLong(data); - } } // [END kms_get_public_key] diff --git a/kms/src/main/java/kms/SignAsymmetric.java b/kms/src/main/java/kms/SignAsymmetric.java index 80d320c74e4..c47d213feff 100644 --- a/kms/src/main/java/kms/SignAsymmetric.java +++ b/kms/src/main/java/kms/SignAsymmetric.java @@ -17,16 +17,11 @@ package kms; // [START kms_sign_asymmetric] -import com.google.cloud.kms.v1.AsymmetricSignRequest; import com.google.cloud.kms.v1.AsymmetricSignResponse; import com.google.cloud.kms.v1.CryptoKeyVersionName; import com.google.cloud.kms.v1.Digest; import com.google.cloud.kms.v1.KeyManagementServiceClient; -import com.google.common.hash.HashCode; -import com.google.common.hash.HashFunction; -import com.google.common.hash.Hashing; import com.google.protobuf.ByteString; -import com.google.protobuf.Int64Value; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; @@ -75,44 +70,14 @@ public void signAsymmetric( // Build the digest object. Digest digest = Digest.newBuilder().setSha256(ByteString.copyFrom(hash)).build(); - // Optional, but recommended: compute digest's CRC32C. See helper below. - long digestCrc32c = getCrc32cAsLong(hash); - // Sign the digest. - AsymmetricSignRequest request = - AsymmetricSignRequest.newBuilder() - .setName(keyVersionName.toString()) - .setDigest(digest) - .setDigestCrc32C(Int64Value.newBuilder().setValue(digestCrc32c).build()) - .build(); - AsymmetricSignResponse response = client.asymmetricSign(request); - - // Optional, but recommended: perform integrity verification on response. - // For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit: - // https://cloud.google.com/kms/docs/data-integrity-guidelines - if (!response.getVerifiedDigestCrc32C()) { - throw new IOException("AsymmetricSign: request to server corrupted"); - } - - // See helper below. - if (!crcMatches(response.getSignatureCrc32C().getValue(), - response.getSignature().toByteArray())) { - throw new IOException("AsymmetricSign: response from server corrupted"); - } + AsymmetricSignResponse result = client.asymmetricSign(keyVersionName, digest); // Get the signature. - byte[] signature = response.getSignature().toByteArray(); + byte[] signature = result.getSignature().toByteArray(); System.out.printf("Signature %s%n", signature); } } - - private long getCrc32cAsLong(byte[] data) { - return Hashing.crc32c().hashBytes(data).padToLong(); - } - - private boolean crcMatches(long expectedCrc, byte[] data) { - return expectedCrc == getCrc32cAsLong(data); - } } // [END kms_sign_asymmetric] diff --git a/kms/src/main/java/kms/SignMac.java b/kms/src/main/java/kms/SignMac.java new file mode 100644 index 00000000000..41a5e905f5a --- /dev/null +++ b/kms/src/main/java/kms/SignMac.java @@ -0,0 +1,69 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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_sign_mac] +import com.google.cloud.kms.v1.CryptoKeyVersionName; +import com.google.cloud.kms.v1.KeyManagementServiceClient; +import com.google.cloud.kms.v1.MacSignResponse; +import com.google.protobuf.ByteString; +import java.io.IOException; +import java.util.Base64; + +public class SignMac { + + public void signMac() 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"; + String data = "Data to sign"; + signMac(projectId, locationId, keyRingId, keyId, keyVersionId, data); + } + + // Sign data with a given mac key. + public void signMac( + String projectId, + String locationId, + String keyRingId, + String keyId, + String keyVersionId, + String data) + 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); + + // Generate an HMAC of the data. + MacSignResponse response = client.macSign(keyVersionName, ByteString.copyFromUtf8(data)); + + // The data comes back as raw bytes, which may include non-printable + // characters. This base64-encodes the result so it can be printed below. + String encodedSignature = Base64.getEncoder().encodeToString(response.getMac().toByteArray()); + System.out.printf("Signature: %s%n", encodedSignature); + } + } +} +// [END kms_sign_mac] diff --git a/kms/src/main/java/kms/VerifyMac.java b/kms/src/main/java/kms/VerifyMac.java new file mode 100644 index 00000000000..209d71c4458 --- /dev/null +++ b/kms/src/main/java/kms/VerifyMac.java @@ -0,0 +1,71 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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_verify_mac] +import com.google.cloud.kms.v1.CryptoKeyVersionName; +import com.google.cloud.kms.v1.KeyManagementServiceClient; +import com.google.cloud.kms.v1.MacVerifyResponse; +import com.google.protobuf.ByteString; +import java.io.IOException; + +public class VerifyMac { + + public void verifyMac() 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"; + String data = "Data to sign"; + byte[] signature = null; + verifyMac(projectId, locationId, keyRingId, keyId, keyVersionId, data, signature); + } + + // Sign data with a given mac key. + public void verifyMac( + String projectId, + String locationId, + String keyRingId, + String keyId, + String keyVersionId, + String data, + byte[] signature) + 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); + + // Verify the signature + MacVerifyResponse response = + client.macVerify( + keyVersionName, ByteString.copyFromUtf8(data), ByteString.copyFrom(signature)); + + // The data comes back as raw bytes, which may include non-printable + // characters. This base64-encodes the result so it can be printed below. + System.out.printf("Success: %s%n", response.getSuccess()); + } + } +} +// [END kms_verify_mac] diff --git a/kms/src/test/java/kms/SnippetsIT.java b/kms/src/test/java/kms/SnippetsIT.java index 52c1911597f..fd2f228ba5f 100644 --- a/kms/src/test/java/kms/SnippetsIT.java +++ b/kms/src/test/java/kms/SnippetsIT.java @@ -33,6 +33,7 @@ import com.google.cloud.kms.v1.KeyRingName; import com.google.cloud.kms.v1.ListCryptoKeyVersionsRequest; import com.google.cloud.kms.v1.LocationName; +import com.google.cloud.kms.v1.MacSignResponse; import com.google.cloud.kms.v1.ProtectionLevel; import com.google.cloud.kms.v1.PublicKey; import com.google.common.base.Strings; @@ -67,7 +68,6 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:AbbreviationAsWordInName") public class SnippetsIT { private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); private static final String LOCATION_ID = "us-east1"; @@ -77,6 +77,7 @@ public class SnippetsIT { private static String ASYMMETRIC_SIGN_EC_KEY_ID; private static String ASYMMETRIC_SIGN_RSA_KEY_ID; private static String HSM_KEY_ID; + private static String MAC_KEY_ID; private static String SYMMETRIC_KEY_ID; private ByteArrayOutputStream stdOut; @@ -100,6 +101,9 @@ public static void beforeAll() throws IOException { HSM_KEY_ID = getRandomId(); createHsmKey(HSM_KEY_ID); + MAC_KEY_ID = getRandomId(); + createMacKey(MAC_KEY_ID); + SYMMETRIC_KEY_ID = getRandomId(); createSymmetricKey(SYMMETRIC_KEY_ID); } @@ -232,6 +236,24 @@ private static CryptoKey createHsmKey(String keyId) throws IOException { } } + private static CryptoKey createMacKey(String keyId) throws IOException { + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + CryptoKey key = + CryptoKey.newBuilder() + .setPurpose(CryptoKeyPurpose.MAC) + .setVersionTemplate( + CryptoKeyVersionTemplate.newBuilder() + .setAlgorithm(CryptoKeyVersionAlgorithm.HMAC_SHA256) + .setProtectionLevel(ProtectionLevel.HSM) + .build()) + .putLabels("foo", "bar") + .putLabels("zip", "zap") + .build(); + CryptoKey createdKey = client.createCryptoKey(getKeyRingName(), keyId, key); + return createdKey; + } + } + private static CryptoKey createSymmetricKey(String keyId) throws IOException { try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { CryptoKey key = @@ -311,6 +333,12 @@ public void testCreateKeyLabels() throws IOException { assertThat(stdOut.toString()).contains("Created key with labels"); } + @Test + public void testCreateKeyMac() throws IOException { + new CreateKeyMac().createKeyMac(PROJECT_ID, LOCATION_ID, KEY_RING_ID, getRandomId()); + assertThat(stdOut.toString()).contains("Created mac key"); + } + @Test public void testCreateKeyRing() throws IOException { new CreateKeyRing().createKeyRing(PROJECT_ID, LOCATION_ID, getRandomId()); @@ -433,6 +461,12 @@ public void testEncryptSymmetric() throws IOException { assertThat(stdOut.toString()).contains("Ciphertext"); } + @Test + public void testGenerateRandomBytes() throws IOException { + new GenerateRandomBytes().generateRandomBytes(PROJECT_ID, LOCATION_ID, 256); + assertThat(stdOut.toString()).contains("Random bytes"); + } + @Test public void testGetKeyVersionAttestation() throws IOException { new GetKeyVersionAttestation() @@ -483,6 +517,12 @@ public void testSignAsymmetric() throws IOException, GeneralSecurityException { assertThat(stdOut.toString()).contains("Signature"); } + @Test + public void testsignMac() throws IOException, GeneralSecurityException { + new SignMac().signMac(PROJECT_ID, LOCATION_ID, KEY_RING_ID, MAC_KEY_ID, "1", "my message"); + assertThat(stdOut.toString()).contains("Signature"); + } + @Test public void testUpdateKeyAddRotation() throws IOException { new UpdateKeyAddRotation() @@ -575,4 +615,27 @@ public void testVerifyAsymmetricRsa() throws IOException, GeneralSecurityExcepti signature); assertThat(stdOut.toString()).contains("Signature"); } + + @Test + public void verifyMac() throws IOException, GeneralSecurityException { + String data = "my data"; + + try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) { + CryptoKeyVersionName versionName = + CryptoKeyVersionName.of(PROJECT_ID, LOCATION_ID, KEY_RING_ID, MAC_KEY_ID, "1"); + + MacSignResponse response = client.macSign(versionName, ByteString.copyFromUtf8(data)); + + new VerifyMac() + .verifyMac( + PROJECT_ID, + LOCATION_ID, + KEY_RING_ID, + MAC_KEY_ID, + "1", + data, + response.getMac().toByteArray()); + assertThat(stdOut.toString()).contains("Success: true"); + } + } } diff --git a/language/snippets/pom.xml b/language/snippets/pom.xml new file mode 100644 index 00000000000..cbff32596c4 --- /dev/null +++ b/language/snippets/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + com.example.language + language-snippets + jar + Google Natural Language Snippets + https://github.com/GoogleCloudPlatform/java-docs-samples/tree/main/language + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + 1.8 + 1.8 + UTF-8 + + + + + + + + com.google.cloud + libraries-bom + 26.1.4 + pom + import + + + + + + + com.google.cloud + google-cloud-language + + + + + junit + junit + 4.13.2 + test + + + com.google.truth + truth + 1.1.3 + test + + + diff --git a/language/snippets/src/main/java/com/example/language/Analyze.java b/language/snippets/src/main/java/com/example/language/Analyze.java new file mode 100644 index 00000000000..aae0d6dcba0 --- /dev/null +++ b/language/snippets/src/main/java/com/example/language/Analyze.java @@ -0,0 +1,388 @@ +/* + * Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.language; + +import com.google.cloud.language.v1.AnalyzeEntitiesRequest; +import com.google.cloud.language.v1.AnalyzeEntitiesResponse; +import com.google.cloud.language.v1.AnalyzeEntitySentimentRequest; +import com.google.cloud.language.v1.AnalyzeEntitySentimentResponse; +import com.google.cloud.language.v1.AnalyzeSentimentResponse; +import com.google.cloud.language.v1.AnalyzeSyntaxRequest; +import com.google.cloud.language.v1.AnalyzeSyntaxResponse; +import com.google.cloud.language.v1.ClassificationCategory; +import com.google.cloud.language.v1.ClassificationModelOptions; +import com.google.cloud.language.v1.ClassificationModelOptions.V2Model; +import com.google.cloud.language.v1.ClassificationModelOptions.V2Model.ContentCategoriesVersion; +import com.google.cloud.language.v1.ClassifyTextRequest; +import com.google.cloud.language.v1.ClassifyTextResponse; +import com.google.cloud.language.v1.Document; +import com.google.cloud.language.v1.Document.Type; +import com.google.cloud.language.v1.EncodingType; +import com.google.cloud.language.v1.Entity; +import com.google.cloud.language.v1.EntityMention; +import com.google.cloud.language.v1.LanguageServiceClient; +import com.google.cloud.language.v1.Sentiment; +import com.google.cloud.language.v1.Token; +import java.util.List; +import java.util.Map; + +/** + * A sample application that uses the Natural Language API to perform entity, sentiment and syntax + * analysis. + */ +public class Analyze { + + /** Detects entities,sentiment and syntax in a document using the Natural Language API. */ + public static void main(String[] args) throws Exception { + if (args.length != 2) { + System.err.println("Usage:"); + System.err.printf( + "\tjava %s \"command\" \"text to analyze\"\n", Analyze.class.getCanonicalName()); + System.exit(1); + } + String command = args[0]; + String text = args[1]; + + if (command.equals("classify")) { + if (text.startsWith("gs://")) { + classifyFile(text); + } else { + classifyText(text); + } + } else if (command.equals("entities")) { + if (text.startsWith("gs://")) { + analyzeEntitiesFile(text); + } else { + analyzeEntitiesText(text); + } + } else if (command.equals("sentiment")) { + if (text.startsWith("gs://")) { + analyzeSentimentFile(text); + } else { + analyzeSentimentText(text); + } + } else if (command.equals("syntax")) { + if (text.startsWith("gs://")) { + analyzeSyntaxFile(text); + } else { + analyzeSyntaxText(text); + } + } else if (command.equals("entities-sentiment")) { + if (text.startsWith("gs://")) { + entitySentimentFile(text); + } else { + entitySentimentText(text); + } + } + } + + /** Identifies entities in the string {@code text}. */ + public static void analyzeEntitiesText(String text) throws Exception { + // [START language_entities_text] + // Instantiate the Language client com.google.cloud.language.v1.LanguageServiceClient + try (LanguageServiceClient language = LanguageServiceClient.create()) { + Document doc = Document.newBuilder().setContent(text).setType(Type.PLAIN_TEXT).build(); + AnalyzeEntitiesRequest request = + AnalyzeEntitiesRequest.newBuilder() + .setDocument(doc) + .setEncodingType(EncodingType.UTF16) + .build(); + + AnalyzeEntitiesResponse response = language.analyzeEntities(request); + + // Print the response + for (Entity entity : response.getEntitiesList()) { + System.out.printf("Entity: %s", entity.getName()); + System.out.printf("Salience: %.3f\n", entity.getSalience()); + System.out.println("Metadata: "); + for (Map.Entry entry : entity.getMetadataMap().entrySet()) { + System.out.printf("%s : %s", entry.getKey(), entry.getValue()); + } + for (EntityMention mention : entity.getMentionsList()) { + System.out.printf("Begin offset: %d\n", mention.getText().getBeginOffset()); + System.out.printf("Content: %s\n", mention.getText().getContent()); + System.out.printf("Type: %s\n\n", mention.getType()); + } + } + } + // [END language_entities_text] + } + + /** Identifies entities in the contents of the object at the given GCS {@code path}. */ + public static void analyzeEntitiesFile(String gcsUri) throws Exception { + // [START language_entities_gcs] + // Instantiate the Language client com.google.cloud.language.v1.LanguageServiceClient + try (LanguageServiceClient language = LanguageServiceClient.create()) { + // Set the GCS Content URI path to the file to be analyzed + Document doc = + Document.newBuilder().setGcsContentUri(gcsUri).setType(Type.PLAIN_TEXT).build(); + AnalyzeEntitiesRequest request = + AnalyzeEntitiesRequest.newBuilder() + .setDocument(doc) + .setEncodingType(EncodingType.UTF16) + .build(); + + AnalyzeEntitiesResponse response = language.analyzeEntities(request); + + // Print the response + for (Entity entity : response.getEntitiesList()) { + System.out.printf("Entity: %s\n", entity.getName()); + System.out.printf("Salience: %.3f\n", entity.getSalience()); + System.out.println("Metadata: "); + for (Map.Entry entry : entity.getMetadataMap().entrySet()) { + System.out.printf("%s : %s", entry.getKey(), entry.getValue()); + } + for (EntityMention mention : entity.getMentionsList()) { + System.out.printf("Begin offset: %d\n", mention.getText().getBeginOffset()); + System.out.printf("Content: %s\n", mention.getText().getContent()); + System.out.printf("Type: %s\n\n", mention.getType()); + } + } + } + // [END language_entities_gcs] + } + + /** Identifies the sentiment in the string {@code text}. */ + public static Sentiment analyzeSentimentText(String text) throws Exception { + // [START language_sentiment_text] + // Instantiate the Language client com.google.cloud.language.v1.LanguageServiceClient + try (LanguageServiceClient language = LanguageServiceClient.create()) { + Document doc = Document.newBuilder().setContent(text).setType(Type.PLAIN_TEXT).build(); + AnalyzeSentimentResponse response = language.analyzeSentiment(doc); + Sentiment sentiment = response.getDocumentSentiment(); + if (sentiment == null) { + System.out.println("No sentiment found"); + } else { + System.out.printf("Sentiment magnitude: %.3f\n", sentiment.getMagnitude()); + System.out.printf("Sentiment score: %.3f\n", sentiment.getScore()); + } + return sentiment; + } + // [END language_sentiment_text] + } + + /** Gets {@link Sentiment} from the contents of the GCS hosted file. */ + public static Sentiment analyzeSentimentFile(String gcsUri) throws Exception { + // [START language_sentiment_gcs] + // Instantiate the Language client com.google.cloud.language.v1.LanguageServiceClient + try (LanguageServiceClient language = LanguageServiceClient.create()) { + Document doc = + Document.newBuilder().setGcsContentUri(gcsUri).setType(Type.PLAIN_TEXT).build(); + AnalyzeSentimentResponse response = language.analyzeSentiment(doc); + Sentiment sentiment = response.getDocumentSentiment(); + if (sentiment == null) { + System.out.println("No sentiment found"); + } else { + System.out.printf("Sentiment magnitude : %.3f\n", sentiment.getMagnitude()); + System.out.printf("Sentiment score : %.3f\n", sentiment.getScore()); + } + return sentiment; + } + // [END language_sentiment_gcs] + } + + /** from the string {@code text}. */ + public static List analyzeSyntaxText(String text) throws Exception { + // [START language_syntax_text] + // Instantiate the Language client com.google.cloud.language.v1.LanguageServiceClient + try (LanguageServiceClient language = LanguageServiceClient.create()) { + Document doc = Document.newBuilder().setContent(text).setType(Type.PLAIN_TEXT).build(); + AnalyzeSyntaxRequest request = + AnalyzeSyntaxRequest.newBuilder() + .setDocument(doc) + .setEncodingType(EncodingType.UTF16) + .build(); + // Analyze the syntax in the given text + AnalyzeSyntaxResponse response = language.analyzeSyntax(request); + // Print the response + for (Token token : response.getTokensList()) { + System.out.printf("\tText: %s\n", token.getText().getContent()); + System.out.printf("\tBeginOffset: %d\n", token.getText().getBeginOffset()); + System.out.printf("Lemma: %s\n", token.getLemma()); + System.out.printf("PartOfSpeechTag: %s\n", token.getPartOfSpeech().getTag()); + System.out.printf("\tAspect: %s\n", token.getPartOfSpeech().getAspect()); + System.out.printf("\tCase: %s\n", token.getPartOfSpeech().getCase()); + System.out.printf("\tForm: %s\n", token.getPartOfSpeech().getForm()); + System.out.printf("\tGender: %s\n", token.getPartOfSpeech().getGender()); + System.out.printf("\tMood: %s\n", token.getPartOfSpeech().getMood()); + System.out.printf("\tNumber: %s\n", token.getPartOfSpeech().getNumber()); + System.out.printf("\tPerson: %s\n", token.getPartOfSpeech().getPerson()); + System.out.printf("\tProper: %s\n", token.getPartOfSpeech().getProper()); + System.out.printf("\tReciprocity: %s\n", token.getPartOfSpeech().getReciprocity()); + System.out.printf("\tTense: %s\n", token.getPartOfSpeech().getTense()); + System.out.printf("\tVoice: %s\n", token.getPartOfSpeech().getVoice()); + System.out.println("DependencyEdge"); + System.out.printf("\tHeadTokenIndex: %d\n", token.getDependencyEdge().getHeadTokenIndex()); + System.out.printf("\tLabel: %s\n\n", token.getDependencyEdge().getLabel()); + } + return response.getTokensList(); + } + // [END language_syntax_text] + } + + /** Get the syntax of the GCS hosted file. */ + public static List analyzeSyntaxFile(String gcsUri) throws Exception { + // [START language_syntax_gcs] + // Instantiate the Language client com.google.cloud.language.v1.LanguageServiceClient + try (LanguageServiceClient language = LanguageServiceClient.create()) { + Document doc = + Document.newBuilder().setGcsContentUri(gcsUri).setType(Type.PLAIN_TEXT).build(); + AnalyzeSyntaxRequest request = + AnalyzeSyntaxRequest.newBuilder() + .setDocument(doc) + .setEncodingType(EncodingType.UTF16) + .build(); + // Analyze the syntax in the given text + AnalyzeSyntaxResponse response = language.analyzeSyntax(request); + // Print the response + for (Token token : response.getTokensList()) { + System.out.printf("\tText: %s\n", token.getText().getContent()); + System.out.printf("\tBeginOffset: %d\n", token.getText().getBeginOffset()); + System.out.printf("Lemma: %s\n", token.getLemma()); + System.out.printf("PartOfSpeechTag: %s\n", token.getPartOfSpeech().getTag()); + System.out.printf("\tAspect: %s\n", token.getPartOfSpeech().getAspect()); + System.out.printf("\tCase: %s\n", token.getPartOfSpeech().getCase()); + System.out.printf("\tForm: %s\n", token.getPartOfSpeech().getForm()); + System.out.printf("\tGender: %s\n", token.getPartOfSpeech().getGender()); + System.out.printf("\tMood: %s\n", token.getPartOfSpeech().getMood()); + System.out.printf("\tNumber: %s\n", token.getPartOfSpeech().getNumber()); + System.out.printf("\tPerson: %s\n", token.getPartOfSpeech().getPerson()); + System.out.printf("\tProper: %s\n", token.getPartOfSpeech().getProper()); + System.out.printf("\tReciprocity: %s\n", token.getPartOfSpeech().getReciprocity()); + System.out.printf("\tTense: %s\n", token.getPartOfSpeech().getTense()); + System.out.printf("\tVoice: %s\n", token.getPartOfSpeech().getVoice()); + System.out.println("DependencyEdge"); + System.out.printf("\tHeadTokenIndex: %d\n", token.getDependencyEdge().getHeadTokenIndex()); + System.out.printf("\tLabel: %s\n\n", token.getDependencyEdge().getLabel()); + } + + return response.getTokensList(); + } + // [END language_syntax_gcs] + } + + /** Detects categories in text using the Language Beta API. */ + public static void classifyText(String text) throws Exception { + // [START language_classify_text] + // Instantiate the Language client com.google.cloud.language.v1.LanguageServiceClient + try (LanguageServiceClient language = LanguageServiceClient.create()) { + // Set content to the text string + Document doc = Document.newBuilder().setContent(text).setType(Type.PLAIN_TEXT).build(); + V2Model v2Model = V2Model.newBuilder() + .setContentCategoriesVersion(ContentCategoriesVersion.V2) + .build(); + ClassificationModelOptions options = + ClassificationModelOptions.newBuilder().setV2Model(v2Model).build(); + ClassifyTextRequest request = + ClassifyTextRequest.newBuilder() + .setDocument(doc) + .setClassificationModelOptions(options) + .build(); + // Detect categories in the given text + ClassifyTextResponse response = language.classifyText(request); + + for (ClassificationCategory category : response.getCategoriesList()) { + System.out.printf( + "Category name : %s, Confidence : %.3f\n", + category.getName(), category.getConfidence()); + } + } + // [END language_classify_text] + } + + /** Detects categories in a GCS hosted file using the Language Beta API. */ + public static void classifyFile(String gcsUri) throws Exception { + // [START language_classify_gcs] + // Instantiate the Language client com.google.cloud.language.v1.LanguageServiceClient + try (LanguageServiceClient language = LanguageServiceClient.create()) { + // Set the GCS content URI path + Document doc = + Document.newBuilder().setGcsContentUri(gcsUri).setType(Type.PLAIN_TEXT).build(); + ClassifyTextRequest request = ClassifyTextRequest.newBuilder().setDocument(doc).build(); + // Detect categories in the given file + ClassifyTextResponse response = language.classifyText(request); + + for (ClassificationCategory category : response.getCategoriesList()) { + System.out.printf( + "Category name : %s, Confidence : %.3f\n", + category.getName(), category.getConfidence()); + } + } + // [END language_classify_gcs] + } + + /** Detects the entity sentiments in the string {@code text} using the Language Beta API. */ + public static void entitySentimentText(String text) throws Exception { + // [START language_entity_sentiment_text] + // Instantiate the Language client com.google.cloud.language.v1.LanguageServiceClient + try (LanguageServiceClient language = LanguageServiceClient.create()) { + Document doc = Document.newBuilder().setContent(text).setType(Type.PLAIN_TEXT).build(); + AnalyzeEntitySentimentRequest request = + AnalyzeEntitySentimentRequest.newBuilder() + .setDocument(doc) + .setEncodingType(EncodingType.UTF16) + .build(); + // Detect entity sentiments in the given string + AnalyzeEntitySentimentResponse response = language.analyzeEntitySentiment(request); + // Print the response + for (Entity entity : response.getEntitiesList()) { + System.out.printf("Entity: %s\n", entity.getName()); + System.out.printf("Salience: %.3f\n", entity.getSalience()); + System.out.printf("Sentiment : %s\n", entity.getSentiment()); + for (EntityMention mention : entity.getMentionsList()) { + System.out.printf("Begin offset: %d\n", mention.getText().getBeginOffset()); + System.out.printf("Content: %s\n", mention.getText().getContent()); + System.out.printf("Magnitude: %.3f\n", mention.getSentiment().getMagnitude()); + System.out.printf("Sentiment score : %.3f\n", mention.getSentiment().getScore()); + System.out.printf("Type: %s\n\n", mention.getType()); + } + } + } + // [END language_entity_sentiment_text] + } + + /** Identifies the entity sentiments in the GCS hosted file using the Language Beta API. */ + public static void entitySentimentFile(String gcsUri) throws Exception { + // [START language_entity_sentiment_gcs] + // Instantiate the Language client com.google.cloud.language.v1.LanguageServiceClient + try (LanguageServiceClient language = LanguageServiceClient.create()) { + Document doc = + Document.newBuilder().setGcsContentUri(gcsUri).setType(Type.PLAIN_TEXT).build(); + AnalyzeEntitySentimentRequest request = + AnalyzeEntitySentimentRequest.newBuilder() + .setDocument(doc) + .setEncodingType(EncodingType.UTF16) + .build(); + // Detect entity sentiments in the given file + AnalyzeEntitySentimentResponse response = language.analyzeEntitySentiment(request); + // Print the response + for (Entity entity : response.getEntitiesList()) { + System.out.printf("Entity: %s\n", entity.getName()); + System.out.printf("Salience: %.3f\n", entity.getSalience()); + System.out.printf("Sentiment : %s\n", entity.getSentiment()); + for (EntityMention mention : entity.getMentionsList()) { + System.out.printf("Begin offset: %d\n", mention.getText().getBeginOffset()); + System.out.printf("Content: %s\n", mention.getText().getContent()); + System.out.printf("Magnitude: %.3f\n", mention.getSentiment().getMagnitude()); + System.out.printf("Sentiment score : %.3f\n", mention.getSentiment().getScore()); + System.out.printf("Type: %s\n\n", mention.getType()); + } + } + } + // [END language_entity_sentiment_gcs] + } +} diff --git a/language/snippets/src/main/java/com/example/language/QuickstartSample.java b/language/snippets/src/main/java/com/example/language/QuickstartSample.java new file mode 100644 index 00000000000..c264dc96dd6 --- /dev/null +++ b/language/snippets/src/main/java/com/example/language/QuickstartSample.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.language; + +// [START language_quickstart] +// Imports the Google Cloud client library +import com.google.cloud.language.v1.Document; +import com.google.cloud.language.v1.Document.Type; +import com.google.cloud.language.v1.LanguageServiceClient; +import com.google.cloud.language.v1.Sentiment; + +public class QuickstartSample { + public static void main(String... args) throws Exception { + // Instantiates a client + try (LanguageServiceClient language = LanguageServiceClient.create()) { + + // The text to analyze + String text = "Hello, world!"; + Document doc = Document.newBuilder().setContent(text).setType(Type.PLAIN_TEXT).build(); + + // Detects the sentiment of the text + Sentiment sentiment = language.analyzeSentiment(doc).getDocumentSentiment(); + + System.out.printf("Text: %s%n", text); + System.out.printf("Sentiment: %s, %s%n", sentiment.getScore(), sentiment.getMagnitude()); + } + } +} +// [END language_quickstart] diff --git a/language/snippets/src/main/java/com/example/language/SetEndpoint.java b/language/snippets/src/main/java/com/example/language/SetEndpoint.java new file mode 100644 index 00000000000..73ab525a4fb --- /dev/null +++ b/language/snippets/src/main/java/com/example/language/SetEndpoint.java @@ -0,0 +1,50 @@ +/* + * 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.language; + +import com.google.cloud.language.v1.Document; +import com.google.cloud.language.v1.LanguageServiceClient; +import com.google.cloud.language.v1.LanguageServiceSettings; +import com.google.cloud.language.v1.Sentiment; +import java.io.IOException; + +class SetEndpoint { + + // Change your endpoint + static void setEndpoint() throws IOException { + // [START language_set_endpoint] + LanguageServiceSettings settings = + LanguageServiceSettings.newBuilder().setEndpoint("eu-language.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. + LanguageServiceClient client = LanguageServiceClient.create(settings); + // [END language_set_endpoint] + + // The text to analyze + String text = "Hello, world!"; + Document doc = Document.newBuilder().setContent(text).setType(Document.Type.PLAIN_TEXT).build(); + + // Detects the sentiment of the text + Sentiment sentiment = client.analyzeSentiment(doc).getDocumentSentiment(); + + System.out.printf("Text: %s%n", text); + System.out.printf("Sentiment: %s, %s%n", sentiment.getScore(), sentiment.getMagnitude()); + client.close(); + } +} diff --git a/language/snippets/src/test/java/com/example/language/AnalyzeIT.java b/language/snippets/src/test/java/com/example/language/AnalyzeIT.java new file mode 100644 index 00000000000..f80ee83cc10 --- /dev/null +++ b/language/snippets/src/test/java/com/example/language/AnalyzeIT.java @@ -0,0 +1,177 @@ +/* + * Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.language; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.language.v1.PartOfSpeech.Tag; +import com.google.cloud.language.v1.Sentiment; +import com.google.cloud.language.v1.Token; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Integration (system) tests for {@link Analyze}. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class AnalyzeIT { + + 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); + } + + @Test + public void analyzeCategoriesInTextReturnsExpectedResult() throws Exception { + Analyze.classifyText( + "Android is a mobile operating system developed by Google, " + + "based on the Linux kernel and designed primarily for touchscreen " + + "mobile devices such as smartphones and tablets."); + String got = bout.toString(); + assertThat(got).contains("Computers & Electronics"); + } + + @Test + public void analyzeCategoriesInFileReturnsExpectedResult() throws Exception { + String gcsFile = "gs://cloud-samples-data/language/android.txt"; + Analyze.classifyFile(gcsFile); + String got = bout.toString(); + assertThat(got).contains("Computers & Electronics"); + } + + @Test + public void analyzeEntities_withEntities_returnsLarryPage() throws Exception { + Analyze.analyzeEntitiesText( + "Larry Page, Google's co-founder, once described the 'perfect search engine' as" + + " something that 'understands exactly what you mean and gives you back exactly what" + + " you want.' Since he spoke those words Google has grown to offer products beyond" + + " search, but the spirit of what he said remains."); + String got = bout.toString(); + assertThat(got).contains("Larry Page"); + } + + @Test + public void analyzeEntities_withEntitiesFile_containsCalifornia() throws Exception { + Analyze.analyzeEntitiesFile("gs://cloud-samples-data/language/entity.txt"); + String got = bout.toString(); + assertThat(got).contains("California"); + } + + @Test + public void analyzeSentimentText_returnPositive() throws Exception { + Sentiment sentiment = + Analyze.analyzeSentimentText( + "Tom Cruise is one of the finest actors in hollywood and a great star!"); + assertThat(sentiment.getMagnitude()).isGreaterThan(0.0F); + assertThat(sentiment.getScore()).isGreaterThan(0.0F); + } + + @Test + public void analyzeSentimentFile_returnPositiveFile() throws Exception { + Sentiment sentiment = + Analyze.analyzeSentimentFile( + "gs://cloud-samples-data/language/" + "sentiment-positive.txt"); + assertThat(sentiment.getMagnitude()).isGreaterThan(0.0F); + assertThat(sentiment.getScore()).isGreaterThan(0.0F); + } + + @Test + public void analyzeSentimentText_returnNegative() throws Exception { + Sentiment sentiment = + Analyze.analyzeSentimentText("That was the worst performance I've seen in a while."); + assertThat(sentiment.getMagnitude()).isGreaterThan(0.0F); + assertThat(sentiment.getScore()).isLessThan(0.0F); + } + + @Test + public void analyzeSentiment_returnNegative() throws Exception { + Sentiment sentiment = + Analyze.analyzeSentimentFile( + "gs://cloud-samples-data/language/" + "sentiment-negative.txt"); + assertThat(sentiment.getMagnitude()).isGreaterThan(0.0F); + assertThat(sentiment.getScore()).isLessThan(0.0F); + } + + @Test + public void analyzeSyntax_partOfSpeech() throws Exception { + List tokens = + Analyze.analyzeSyntaxText("President Obama was elected for the second term"); + + List got = + tokens.stream().map(e -> e.getPartOfSpeech().getTag()).collect(Collectors.toList()); + + assertThat(got) + .containsExactly( + Tag.NOUN, Tag.NOUN, Tag.VERB, Tag.VERB, Tag.ADP, Tag.DET, Tag.ADJ, Tag.NOUN) + .inOrder(); + } + + @Test + public void analyzeSyntax_partOfSpeechFile() throws Exception { + List token = + Analyze.analyzeSyntaxFile("gs://cloud-samples-data/language/" + "syntax-sentence.txt"); + + List got = + token.stream().map(e -> e.getPartOfSpeech().getTag()).collect(Collectors.toList()); + assertThat(got) + .containsExactly(Tag.DET, Tag.VERB, Tag.DET, Tag.ADJ, Tag.NOUN, Tag.PUNCT) + .inOrder(); + } + + @Test + public void analyzeEntitySentimentTextReturnsExpectedResult() throws Exception { + Analyze.entitySentimentText( + "Oranges, grapes, and apples can be " + + "found in the cafeterias located in Mountain View, Seattle, and London."); + String got = bout.toString(); + assertThat(got).contains("Seattle"); + } + + @Test + public void analyzeEntitySentimentTextEncodedReturnsExpectedResult() throws Exception { + Analyze.entitySentimentText("foo→bar"); + String got = bout.toString(); + assertThat(got).contains("offset: 4"); + } + + @Test + public void analyzeEntitySentimenFileReturnsExpectedResult() throws Exception { + Analyze.entitySentimentFile("gs://cloud-samples-data/language/president.txt"); + String got = bout.toString(); + assertThat(got).contains("Kennedy"); + } +} diff --git a/language/snippets/src/test/java/com/example/language/QuickstartSampleIT.java b/language/snippets/src/test/java/com/example/language/QuickstartSampleIT.java new file mode 100644 index 00000000000..15a89ff5d96 --- /dev/null +++ b/language/snippets/src/test/java/com/example/language/QuickstartSampleIT.java @@ -0,0 +1,62 @@ +/* + * Copyright 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.language; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for quickstart sample. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class QuickstartSampleIT { + 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); + } + + @Test + public void testQuickstart() throws Exception { + // Act + QuickstartSample.main(); + + // Assert + String got = bout.toString(); + assertThat(got).contains("Text: Hello, world!"); + assertThat(got).contains("Sentiment: "); + } +} diff --git a/language/snippets/src/test/java/com/example/language/SetEndpointIT.java b/language/snippets/src/test/java/com/example/language/SetEndpointIT.java new file mode 100644 index 00000000000..f464f472735 --- /dev/null +++ b/language/snippets/src/test/java/com/example/language/SetEndpointIT.java @@ -0,0 +1,63 @@ +/* + * 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.language; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for Natural Language Set Endpoint */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class SetEndpointIT { + + 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); + } + + @Test + public void testSetEndpoint() throws IOException { + // Act + SetEndpoint.setEndpoint(); + + // Assert + String got = bout.toString(); + assertThat(got).contains("Sentiment"); + } +} diff --git a/monitoring/v3/pom.xml b/monitoring/v3/pom.xml index 4916c62a4e0..e59b03db45e 100644 --- a/monitoring/v3/pom.xml +++ b/monitoring/v3/pom.xml @@ -19,7 +19,7 @@ com.example.monitoring monitoring-google-cloud-v3-samples 0.1-SNAPSHOT - jar + jar @@ -44,13 +44,13 @@ import - + com.google.cloud google-cloud-monitoring - + commons-cli commons-cli diff --git a/recaptcha_enterprise/pom.xml b/recaptcha_enterprise/pom.xml new file mode 100644 index 00000000000..86240c555be --- /dev/null +++ b/recaptcha_enterprise/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + com.google.cloud + recaptcha-enterprise-snippets + jar + Google reCAPTCHA Enterprise Snippets + https://github.com/googleapis/java-recaptchaenterprise + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + 1.8 + 1.8 + UTF-8 + + + + + + + + com.google.cloud + libraries-bom + 26.1.4 + pom + import + + + + + + + com.google.cloud + google-cloud-recaptchaenterprise + + + + junit + junit + 4.13.2 + test + + + com.google.truth + truth + 1.1.3 + test + + + + + diff --git a/recaptcha_enterprise/snippets/src/main/java/app/Main.java b/recaptcha_enterprise/snippets/src/main/java/app/Main.java new file mode 100644 index 00000000000..d51c4dcb4eb --- /dev/null +++ b/recaptcha_enterprise/snippets/src/main/java/app/Main.java @@ -0,0 +1,28 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Main { + + public static void main(String[] args) { + SpringApplication.run(Main.class, args); + } +} diff --git a/recaptcha_enterprise/snippets/src/main/java/app/MainController.java b/recaptcha_enterprise/snippets/src/main/java/app/MainController.java new file mode 100644 index 00000000000..473772ea4a4 --- /dev/null +++ b/recaptcha_enterprise/snippets/src/main/java/app/MainController.java @@ -0,0 +1,37 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.servlet.ModelAndView; + +@Controller +@RequestMapping +public class MainController { + + @GetMapping(value = "/") + public static ModelAndView landingPage( + @RequestParam("recaptchaSiteKey") String recaptchaSiteKey) { + ModelMap map = new ModelAndView().getModelMap(); + map.put("siteKey", recaptchaSiteKey); + return new ModelAndView("index", map); + } +} diff --git a/recaptcha_enterprise/snippets/src/main/java/recaptcha/AnnotateAssessment.java b/recaptcha_enterprise/snippets/src/main/java/recaptcha/AnnotateAssessment.java new file mode 100644 index 00000000000..8c3e8d5306c --- /dev/null +++ b/recaptcha_enterprise/snippets/src/main/java/recaptcha/AnnotateAssessment.java @@ -0,0 +1,68 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package recaptcha; + +// [START recaptcha_enterprise_annotate_assessment] + +import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceClient; +import com.google.recaptchaenterprise.v1.AnnotateAssessmentRequest; +import com.google.recaptchaenterprise.v1.AnnotateAssessmentRequest.Annotation; +import com.google.recaptchaenterprise.v1.AnnotateAssessmentRequest.Reason; +import com.google.recaptchaenterprise.v1.AnnotateAssessmentResponse; +import com.google.recaptchaenterprise.v1.AssessmentName; +import java.io.IOException; + +public class AnnotateAssessment { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectID = "project-id"; + String assessmentId = "assessment-id"; + annotateAssessment(projectID, assessmentId); + } + + /** + * Pre-requisite: Create an assessment before annotating. + * + *

Annotate an assessment to provide feedback on the correctness of recaptcha prediction. + * + * @param projectID: GCloud Project id + * @param assessmentId: Value of the 'name' field returned from the CreateAssessment call. + */ + public static void annotateAssessment(String projectID, String assessmentId) 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 `client.close()` method on the client to safely + // clean up any remaining background resources. + try (RecaptchaEnterpriseServiceClient client = RecaptchaEnterpriseServiceClient.create()) { + // Build the annotation request. + // For more info on when/how to annotate, see: + // https://cloud.google.com/recaptcha-enterprise/docs/annotate-assessment#when_to_annotate + AnnotateAssessmentRequest annotateAssessmentRequest = + AnnotateAssessmentRequest.newBuilder() + .setName(AssessmentName.of(projectID, assessmentId).toString()) + .setAnnotation(Annotation.FRAUDULENT) + .addReasons(Reason.FAILED_TWO_FACTOR) + .build(); + + // Empty response is sent back. + AnnotateAssessmentResponse response = client.annotateAssessment(annotateAssessmentRequest); + System.out.println("Annotated response sent successfully ! " + response); + } + } +} +// [END recaptcha_enterprise_annotate_assessment] diff --git a/recaptcha_enterprise/snippets/src/main/java/recaptcha/CreateAssessment.java b/recaptcha_enterprise/snippets/src/main/java/recaptcha/CreateAssessment.java new file mode 100644 index 00000000000..8fed3a1c8ca --- /dev/null +++ b/recaptcha_enterprise/snippets/src/main/java/recaptcha/CreateAssessment.java @@ -0,0 +1,111 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package recaptcha; + +// [START recaptcha_enterprise_create_assessment] + +import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceClient; +import com.google.recaptchaenterprise.v1.Assessment; +import com.google.recaptchaenterprise.v1.CreateAssessmentRequest; +import com.google.recaptchaenterprise.v1.Event; +import com.google.recaptchaenterprise.v1.ProjectName; +import com.google.recaptchaenterprise.v1.RiskAnalysis.ClassificationReason; +import java.io.IOException; + +public class CreateAssessment { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectID = "project-id"; + String recaptchaSiteKey = "recaptcha-site-key"; + String token = "action-token"; + String recaptchaAction = "action-name"; + + createAssessment(projectID, recaptchaSiteKey, token, recaptchaAction); + } + + /** + * Create an assessment to analyze the risk of an UI action. Assessment approach is the same for + * both 'score' and 'checkbox' type recaptcha site keys. + * + * @param projectID : GCloud Project ID + * @param recaptchaSiteKey : Site key obtained by registering a domain/app to use recaptcha + * services. (score/ checkbox type) + * @param token : The token obtained from the client on passing the recaptchaSiteKey. + * @param recaptchaAction : Action name corresponding to the token. + */ + public static void createAssessment( + String projectID, String recaptchaSiteKey, String token, String recaptchaAction) + 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 `client.close()` method on the client to safely + // clean up any remaining background resources. + try (RecaptchaEnterpriseServiceClient client = RecaptchaEnterpriseServiceClient.create()) { + + // Set the properties of the event to be tracked. + Event event = Event.newBuilder().setSiteKey(recaptchaSiteKey).setToken(token).build(); + + // Build the assessment request. + CreateAssessmentRequest createAssessmentRequest = + CreateAssessmentRequest.newBuilder() + .setParent(ProjectName.of(projectID).toString()) + .setAssessment(Assessment.newBuilder().setEvent(event).build()) + .build(); + + Assessment response = client.createAssessment(createAssessmentRequest); + + // Check if the token is valid. + if (!response.getTokenProperties().getValid()) { + System.out.println( + "The CreateAssessment call failed because the token was: " + + response.getTokenProperties().getInvalidReason().name()); + return; + } + + // Check if the expected action was executed. + // (If the key is checkbox type and 'action' attribute wasn't set, skip this check.) + if (!response.getTokenProperties().getAction().equals(recaptchaAction)) { + System.out.println( + "The action attribute in reCAPTCHA tag is: " + + response.getTokenProperties().getAction()); + System.out.println( + "The action attribute in the reCAPTCHA tag " + + "does not match the action (" + + recaptchaAction + + ") you are expecting to score"); + return; + } + + // Get the reason(s) and the risk score. + // For more information on interpreting the assessment, + // see: https://cloud.google.com/recaptcha-enterprise/docs/interpret-assessment + for (ClassificationReason reason : response.getRiskAnalysis().getReasonsList()) { + System.out.println(reason); + } + + float recaptchaScore = response.getRiskAnalysis().getScore(); + System.out.println("The reCAPTCHA score is: " + recaptchaScore); + + // Get the assessment name (id). Use this to annotate the assessment. + String assessmentName = response.getName(); + System.out.println( + "Assessment name: " + assessmentName.substring(assessmentName.lastIndexOf("/") + 1)); + } + } +} +// [END recaptcha_enterprise_create_assessment] diff --git a/recaptcha_enterprise/snippets/src/main/java/recaptcha/CreateSiteKey.java b/recaptcha_enterprise/snippets/src/main/java/recaptcha/CreateSiteKey.java new file mode 100644 index 00000000000..635a6c5e4a7 --- /dev/null +++ b/recaptcha_enterprise/snippets/src/main/java/recaptcha/CreateSiteKey.java @@ -0,0 +1,80 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package recaptcha; + +// [START recaptcha_enterprise_create_site_key] + +import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceClient; +import com.google.recaptchaenterprise.v1.CreateKeyRequest; +import com.google.recaptchaenterprise.v1.Key; +import com.google.recaptchaenterprise.v1.ProjectName; +import com.google.recaptchaenterprise.v1.WebKeySettings; +import com.google.recaptchaenterprise.v1.WebKeySettings.IntegrationType; +import java.io.IOException; + +public class CreateSiteKey { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectID = "your-project-id"; + String domainName = "domain-name"; + + createSiteKey(projectID, domainName); + } + + /** + * Create reCAPTCHA Site key which binds a domain name to a unique key. + * + * @param projectID : GCloud Project ID. + * @param domainName : Specify the domain name in which the reCAPTCHA should be activated. + */ + public static String createSiteKey(String projectID, String domainName) 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 `client.close()` method on the client to safely + // clean up any remaining background resources. + try (RecaptchaEnterpriseServiceClient client = RecaptchaEnterpriseServiceClient.create()) { + + // Set the type of reCAPTCHA to be displayed. + // For different types, see: https://cloud.google.com/recaptcha-enterprise/docs/keys + Key scoreKey = + Key.newBuilder() + .setDisplayName("any_descriptive_name_for_the_key") + .setWebSettings( + WebKeySettings.newBuilder() + .addAllowedDomains(domainName) + .setAllowAmpTraffic(false) + .setIntegrationType(IntegrationType.SCORE) + .build()) + .build(); + + CreateKeyRequest createKeyRequest = + CreateKeyRequest.newBuilder() + .setParent(ProjectName.of(projectID).toString()) + .setKey(scoreKey) + .build(); + + // Get the name of the created reCAPTCHA site key. + Key response = client.createKey(createKeyRequest); + String keyName = response.getName(); + String recaptchaSiteKey = keyName.substring(keyName.lastIndexOf("/") + 1); + System.out.println("reCAPTCHA Site key created successfully. Site Key: " + recaptchaSiteKey); + return recaptchaSiteKey; + } + } +} +// [END recaptcha_enterprise_create_site_key] diff --git a/recaptcha_enterprise/snippets/src/main/java/recaptcha/DeleteSiteKey.java b/recaptcha_enterprise/snippets/src/main/java/recaptcha/DeleteSiteKey.java new file mode 100644 index 00000000000..9089c5ee4e6 --- /dev/null +++ b/recaptcha_enterprise/snippets/src/main/java/recaptcha/DeleteSiteKey.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package recaptcha; + +// [START recaptcha_enterprise_delete_site_key] + +import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceClient; +import com.google.recaptchaenterprise.v1.DeleteKeyRequest; +import com.google.recaptchaenterprise.v1.KeyName; +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class DeleteSiteKey { + + 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 recaptchaSiteKey = "recaptcha-site-key"; + + deleteSiteKey(projectID, recaptchaSiteKey); + } + + /** + * Delete the given reCAPTCHA site key present under the project ID. + * + * @param projectID: GCloud Project ID. + * @param recaptchaSiteKey: Specify the site key to be deleted. + */ + public static void deleteSiteKey(String projectID, String recaptchaSiteKey) + 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 `client.close()` method on the client to safely + // clean up any remaining background resources. + try (RecaptchaEnterpriseServiceClient client = RecaptchaEnterpriseServiceClient.create()) { + + // Set the project ID and reCAPTCHA site key. + DeleteKeyRequest deleteKeyRequest = + DeleteKeyRequest.newBuilder() + .setName(KeyName.of(projectID, recaptchaSiteKey).toString()) + .build(); + + client.deleteKeyCallable().futureCall(deleteKeyRequest).get(5, TimeUnit.SECONDS); + System.out.println("reCAPTCHA Site key successfully deleted !"); + } + } +} +// [END recaptcha_enterprise_delete_site_key] diff --git a/recaptcha_enterprise/snippets/src/main/java/recaptcha/GetMetrics.java b/recaptcha_enterprise/snippets/src/main/java/recaptcha/GetMetrics.java new file mode 100644 index 00000000000..018dc73faac --- /dev/null +++ b/recaptcha_enterprise/snippets/src/main/java/recaptcha/GetMetrics.java @@ -0,0 +1,70 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package recaptcha; + +// [START recaptcha_enterprise_get_metrics_site_key] + +import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceClient; +import com.google.recaptchaenterprise.v1.GetMetricsRequest; +import com.google.recaptchaenterprise.v1.Metrics; +import com.google.recaptchaenterprise.v1.MetricsName; +import com.google.recaptchaenterprise.v1.ScoreMetrics; +import java.io.IOException; + +public class GetMetrics { + + public static void main(String[] args) throws IOException { + String projectId = "project-id"; + String recaptchaSiteKey = "recaptcha-site-key"; + + getMetrics(projectId, recaptchaSiteKey); + } + + /** + * Get metrics specific to a recaptcha site key. E.g: score bucket count for a key or number of + * times the checkbox key failed/ passed etc., + * + * @param projectId: Google Cloud Project Id. + * @param recaptchaSiteKey: Specify the site key to get metrics. + */ + public static void getMetrics(String projectId, String recaptchaSiteKey) 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 `client.close()` method on the client to safely + // clean up any remaining background resources. + try (RecaptchaEnterpriseServiceClient client = RecaptchaEnterpriseServiceClient.create()) { + + GetMetricsRequest getMetricsRequest = + GetMetricsRequest.newBuilder() + .setName(MetricsName.of(projectId, recaptchaSiteKey).toString()) + .build(); + + Metrics response = client.getMetrics(getMetricsRequest); + + // Retrieve the metrics you want from the key. + // If the site key is checkbox type: then use response.getChallengeMetricsList() instead of + // response.getScoreMetricsList() + for (ScoreMetrics scoreMetrics : response.getScoreMetricsList()) { + // Each ScoreMetrics is in the granularity of one day. + int scoreBucketCount = scoreMetrics.getOverallMetrics().getScoreBucketsCount(); + System.out.println(scoreBucketCount); + } + System.out.printf("Retrieved the bucket count for score based key: %s", recaptchaSiteKey); + } + } +} +// [END recaptcha_enterprise_get_metrics_site_key] diff --git a/recaptcha_enterprise/snippets/src/main/java/recaptcha/GetSiteKey.java b/recaptcha_enterprise/snippets/src/main/java/recaptcha/GetSiteKey.java new file mode 100644 index 00000000000..74eda6a1b2f --- /dev/null +++ b/recaptcha_enterprise/snippets/src/main/java/recaptcha/GetSiteKey.java @@ -0,0 +1,70 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package recaptcha; + +// [START recaptcha_enterprise_get_site_key] + +import com.google.api.core.ApiFuture; +import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceClient; +import com.google.recaptchaenterprise.v1.GetKeyRequest; +import com.google.recaptchaenterprise.v1.Key; +import com.google.recaptchaenterprise.v1.KeyName; +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class GetSiteKey { + + public static void main(String[] args) + throws IOException, InterruptedException, ExecutionException, TimeoutException { + // TODO(developer): Replace these variables before running the sample. + String projectID = "your-project-id"; + String recaptchaSiteKey = "recaptcha-site-key"; + + getSiteKey(projectID, recaptchaSiteKey); + } + + /** + * Get the reCAPTCHA site key present under the project ID. + * + * @param projectID: GCloud Project ID. + * @param recaptchaSiteKey: Specify the site key to get the details. + */ + public static void getSiteKey(String projectID, String recaptchaSiteKey) + throws IOException, InterruptedException, ExecutionException, 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 `client.close()` method on the client to safely + // clean up any remaining background resources. + try (RecaptchaEnterpriseServiceClient client = RecaptchaEnterpriseServiceClient.create()) { + + // Construct the "GetSiteKey" request. + GetKeyRequest getKeyRequest = + GetKeyRequest.newBuilder() + .setName(KeyName.of(projectID, recaptchaSiteKey).toString()) + .build(); + + // Wait for the operation to complete. + ApiFuture futureCall = client.getKeyCallable().futureCall(getKeyRequest); + Key key = futureCall.get(5, TimeUnit.SECONDS); + + System.out.println("Successfully obtained the key !" + key.getName()); + } + } +} +// [END recaptcha_enterprise_get_site_key] diff --git a/recaptcha_enterprise/snippets/src/main/java/recaptcha/ListSiteKeys.java b/recaptcha_enterprise/snippets/src/main/java/recaptcha/ListSiteKeys.java new file mode 100644 index 00000000000..504de8225ba --- /dev/null +++ b/recaptcha_enterprise/snippets/src/main/java/recaptcha/ListSiteKeys.java @@ -0,0 +1,61 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package recaptcha; + +// [START recaptcha_enterprise_list_site_keys] + +import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceClient; +import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceClient.ListKeysPagedResponse; +import com.google.recaptchaenterprise.v1.Key; +import com.google.recaptchaenterprise.v1.ListKeysRequest; +import com.google.recaptchaenterprise.v1.ProjectName; +import java.io.IOException; + +public class ListSiteKeys { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectID = "your-project-id"; + + listSiteKeys(projectID); + } + + /** + * List all keys present under the given project ID. + * + * @param projectID : GCloud Project ID. + */ + public static ListKeysPagedResponse listSiteKeys(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 `client.close()` method on the client to safely + // clean up any remaining background resources. + try (RecaptchaEnterpriseServiceClient client = RecaptchaEnterpriseServiceClient.create()) { + // Set the project ID to list the keys present in it. + ListKeysRequest listKeysRequest = + ListKeysRequest.newBuilder().setParent(ProjectName.of(projectID).toString()).build(); + + ListKeysPagedResponse response = client.listKeys(listKeysRequest); + System.out.println("Listing reCAPTCHA site keys: "); + for (Key key : response.iterateAll()) { + System.out.println(key.getName()); + } + return response; + } + } +} +// [END recaptcha_enterprise_list_site_keys] diff --git a/recaptcha_enterprise/snippets/src/main/java/recaptcha/MigrateKey.java b/recaptcha_enterprise/snippets/src/main/java/recaptcha/MigrateKey.java new file mode 100644 index 00000000000..e529339e1d3 --- /dev/null +++ b/recaptcha_enterprise/snippets/src/main/java/recaptcha/MigrateKey.java @@ -0,0 +1,70 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package recaptcha; + +// [START recaptcha_enterprise_migrate_site_key] + +import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceClient; +import com.google.recaptchaenterprise.v1.Key; +import com.google.recaptchaenterprise.v1.KeyName; +import com.google.recaptchaenterprise.v1.MigrateKeyRequest; +import java.io.IOException; + +public class MigrateKey { + + public static void main(String[] args) throws IOException { + String projectId = "project-id"; + String recaptchaSiteKey = "recaptcha-site-key"; + + migrateKey(projectId, recaptchaSiteKey); + } + + /** + * Migrate a key from reCAPTCHA (non-Enterprise) to reCAPTCHA Enterprise. If you created the key + * using Admin console: https://www.google.com/recaptcha/admin/site, then use this API to migrate + * to reCAPTCHA Enterprise. For more info, see: + * https://cloud.google.com/recaptcha-enterprise/docs/migrate-recaptcha + * + * @param projectId: Google Cloud Project Id. + * @param recaptchaSiteKey: Specify the site key to migrate. + */ + public static void migrateKey(String projectId, String recaptchaSiteKey) 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 `client.close()` method on the client to safely + // clean up any remaining background resources. + try (RecaptchaEnterpriseServiceClient client = RecaptchaEnterpriseServiceClient.create()) { + + // Specify the key name to migrate. + MigrateKeyRequest migrateKeyRequest = + MigrateKeyRequest.newBuilder() + .setName(KeyName.of(projectId, recaptchaSiteKey).toString()) + .build(); + + Key response = client.migrateKey(migrateKeyRequest); + + // To verify if the site key has been migrated, use 'ListSiteKeys' and check if the + // key is present. + for (Key key : recaptcha.ListSiteKeys.listSiteKeys(projectId).iterateAll()) { + if (key.equals(response)) { + System.out.printf("Key migrated successfully: %s", recaptchaSiteKey); + } + } + } + } +} +// [END recaptcha_enterprise_migrate_site_key] diff --git a/recaptcha_enterprise/snippets/src/main/java/recaptcha/UpdateSiteKey.java b/recaptcha_enterprise/snippets/src/main/java/recaptcha/UpdateSiteKey.java new file mode 100644 index 00000000000..c0e7d3e1311 --- /dev/null +++ b/recaptcha_enterprise/snippets/src/main/java/recaptcha/UpdateSiteKey.java @@ -0,0 +1,93 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package recaptcha; + +// [START recaptcha_enterprise_update_site_key] + +import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceClient; +import com.google.recaptchaenterprise.v1.GetKeyRequest; +import com.google.recaptchaenterprise.v1.Key; +import com.google.recaptchaenterprise.v1.KeyName; +import com.google.recaptchaenterprise.v1.UpdateKeyRequest; +import com.google.recaptchaenterprise.v1.WebKeySettings; +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +public class UpdateSiteKey { + + public static void main(String[] args) + throws IOException, InterruptedException, ExecutionException, TimeoutException { + // TODO(developer): Replace these variables before running the sample. + String projectID = "your-project-id"; + String recaptchaSiteKeyID = "recaptcha-site-key-id"; + String domainName = "domain-name"; + + updateSiteKey(projectID, recaptchaSiteKeyID, domainName); + } + + /** + * Update the properties of the given site key present under the project id. + * + * @param projectID: GCloud Project ID. + * @param recaptchaSiteKeyID: Specify the site key. + * @param domainName: Specify the domain name for which the settings should be updated. + */ + public static void updateSiteKey(String projectID, String recaptchaSiteKeyID, String domainName) + throws IOException, InterruptedException, ExecutionException, 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 `client.close()` method on the client to safely + // clean up any remaining background resources. + try (RecaptchaEnterpriseServiceClient client = RecaptchaEnterpriseServiceClient.create()) { + + // Set the name and the new settings for the key. + UpdateKeyRequest updateKeyRequest = + UpdateKeyRequest.newBuilder() + .setKey( + Key.newBuilder() + .setDisplayName("any descriptive name for the key") + .setName(KeyName.of(projectID, recaptchaSiteKeyID).toString()) + .setWebSettings( + WebKeySettings.newBuilder() + .setAllowAmpTraffic(true) + .addAllowedDomains(domainName) + .build()) + .build()) + .build(); + + client.updateKeyCallable().futureCall(updateKeyRequest).get(); + + // Check if the key has been updated. + GetKeyRequest getKeyRequest = + GetKeyRequest.newBuilder() + .setName(KeyName.of(projectID, recaptchaSiteKeyID).toString()) + .build(); + Key response = client.getKey(getKeyRequest); + + // Get the changed property. + boolean allowedAmpTraffic = response.getWebSettings().getAllowAmpTraffic(); + if (!allowedAmpTraffic) { + System.out.println( + "Error! reCAPTCHA Site key property hasn't been updated. Please try again !"); + return; + } + System.out.println("reCAPTCHA Site key successfully updated !"); + } + } +} +// [END recaptcha_enterprise_update_site_key] diff --git a/recaptcha_enterprise/snippets/src/main/java/recaptcha/account_defender/AccountDefenderAssessment.java b/recaptcha_enterprise/snippets/src/main/java/recaptcha/account_defender/AccountDefenderAssessment.java new file mode 100644 index 00000000000..b51b292c24f --- /dev/null +++ b/recaptcha_enterprise/snippets/src/main/java/recaptcha/account_defender/AccountDefenderAssessment.java @@ -0,0 +1,158 @@ +/* + * 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 account_defender; + +// [START recaptcha_enterprise_account_defender_assessment] + +import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceClient; +import com.google.protobuf.ByteString; +import com.google.recaptchaenterprise.v1.AccountDefenderAssessment.AccountDefenderLabel; +import com.google.recaptchaenterprise.v1.Assessment; +import com.google.recaptchaenterprise.v1.CreateAssessmentRequest; +import com.google.recaptchaenterprise.v1.Event; +import com.google.recaptchaenterprise.v1.ProjectName; +import com.google.recaptchaenterprise.v1.RiskAnalysis.ClassificationReason; +import com.google.recaptchaenterprise.v1.TokenProperties; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import java.util.UUID; + +public class AccountDefenderAssessment { + + public static void main(String[] args) throws IOException, NoSuchAlgorithmException { + // TODO(developer): Replace these variables before running the sample. + // projectId: Google Cloud Project ID + String projectId = "project-id"; + + // recaptchaSiteKey: Site key obtained by registering a domain/app to use recaptcha + // services. + String recaptchaSiteKey = "recaptcha-site-key"; + + // token: The token obtained from the client on passing the recaptchaSiteKey. + // To get the token, integrate the recaptchaSiteKey with frontend. See, + // https://cloud.google.com/recaptcha-enterprise/docs/instrument-web-pages#frontend_integration_score + String token = "recaptcha-token"; + + // recaptchaAction: The action name corresponding to the token. + String recaptchaAction = "recaptcha-action"; + + // Unique ID of the customer, such as email, customer ID, etc. + String userIdentifier = "default" + UUID.randomUUID().toString().split("-")[0]; + + // Hash the unique customer ID using HMAC SHA-256. + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hashBytes = digest.digest(userIdentifier.getBytes(StandardCharsets.UTF_8)); + ByteString hashedAccountId = ByteString.copyFrom(hashBytes); + + accountDefenderAssessment(projectId, recaptchaSiteKey, token, recaptchaAction, hashedAccountId); + } + + /** + * This assessment detects account takeovers. See, + * https://cloud.google.com/recaptcha-enterprise/docs/account-takeovers The input is the hashed + * account id. Result tells if the action represents an account takeover. You can optionally + * trigger a Multi-Factor Authentication based on the result. + */ + public static void accountDefenderAssessment( + String projectId, + String recaptchaSiteKey, + String token, + String recaptchaAction, + ByteString hashedAccountId) + throws IOException { + try (RecaptchaEnterpriseServiceClient client = RecaptchaEnterpriseServiceClient.create()) { + + // Set the properties of the event to be tracked. + Event event = + Event.newBuilder() + .setSiteKey(recaptchaSiteKey) + .setToken(token) + // Set the hashed account id (of the user). + // Recommended approach: HMAC SHA256 along with salt (or secret key). + .setHashedAccountId(hashedAccountId) + .build(); + + // Build the assessment request. + CreateAssessmentRequest createAssessmentRequest = + CreateAssessmentRequest.newBuilder() + .setParent(ProjectName.of(projectId).toString()) + .setAssessment(Assessment.newBuilder().setEvent(event).build()) + .build(); + + Assessment response = client.createAssessment(createAssessmentRequest); + + // Check integrity of the response token. + if (!checkTokenIntegrity(response.getTokenProperties(), recaptchaAction)) { + return; + } + + // Get the reason(s) and the reCAPTCHA risk score. + // For more information on interpreting the assessment, + // see: https://cloud.google.com/recaptcha-enterprise/docs/interpret-assessment + for (ClassificationReason reason : response.getRiskAnalysis().getReasonsList()) { + System.out.println(reason); + } + float recaptchaScore = response.getRiskAnalysis().getScore(); + System.out.println("The reCAPTCHA score is: " + recaptchaScore); + String assessmentName = response.getName(); + System.out.println( + "Assessment name: " + assessmentName.substring(assessmentName.lastIndexOf("/") + 1)); + + // Get the Account Defender result. + com.google.recaptchaenterprise.v1.AccountDefenderAssessment accountDefenderAssessment = + response.getAccountDefenderAssessment(); + System.out.println(accountDefenderAssessment); + + // Get Account Defender label. + List defenderResult = + response.getAccountDefenderAssessment().getLabelsList(); + // Based on the result, can you choose next steps. + // If the 'defenderResult' field is empty, it indicates that Account Defender did not have + // anything to add to the score. + // Few result labels: ACCOUNT_DEFENDER_LABEL_UNSPECIFIED, PROFILE_MATCH, + // SUSPICIOUS_LOGIN_ACTIVITY, SUSPICIOUS_ACCOUNT_CREATION, RELATED_ACCOUNTS_NUMBER_HIGH. + // For more information on interpreting the assessment, see: + // https://cloud.google.com/recaptcha-enterprise/docs/account-defender#interpret-assessment-details + System.out.println("Account Defender Assessment Result: " + defenderResult); + } + } + + private static boolean checkTokenIntegrity( + TokenProperties tokenProperties, String recaptchaAction) { + // Check if the token is valid. + if (!tokenProperties.getValid()) { + System.out.println( + "The Account Defender Assessment call failed because the token was: " + + tokenProperties.getInvalidReason().name()); + return false; + } + + // Check if the expected action was executed. + if (!tokenProperties.getAction().equals(recaptchaAction)) { + System.out.printf( + "The action attribute in the reCAPTCHA tag '%s' does not match " + + "the action '%s' you are expecting to score", + tokenProperties.getAction(), recaptchaAction); + return false; + } + return true; + } +} +// [END recaptcha_enterprise_account_defender_assessment] diff --git a/recaptcha_enterprise/snippets/src/main/java/recaptcha/account_defender/AnnotateAccountDefenderAssessment.java b/recaptcha_enterprise/snippets/src/main/java/recaptcha/account_defender/AnnotateAccountDefenderAssessment.java new file mode 100644 index 00000000000..ca8129f3c0b --- /dev/null +++ b/recaptcha_enterprise/snippets/src/main/java/recaptcha/account_defender/AnnotateAccountDefenderAssessment.java @@ -0,0 +1,72 @@ +/* + * 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 account_defender; + +// [START recaptcha_enterprise_annotate_account_defender_assessment] + +import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceClient; +import com.google.protobuf.ByteString; +import com.google.recaptchaenterprise.v1.AnnotateAssessmentRequest; +import com.google.recaptchaenterprise.v1.AnnotateAssessmentRequest.Annotation; +import com.google.recaptchaenterprise.v1.AnnotateAssessmentRequest.Reason; +import com.google.recaptchaenterprise.v1.AnnotateAssessmentResponse; +import com.google.recaptchaenterprise.v1.AssessmentName; +import java.io.IOException; +import java.security.NoSuchAlgorithmException; + +public class AnnotateAccountDefenderAssessment { + + public static void main(String[] args) throws IOException, NoSuchAlgorithmException { + // TODO(developer): Replace these variables before running the sample. + // projectID: GCloud Project id. + String projectID = "project-id"; + + // assessmentId: Value of the 'name' field returned from the CreateAssessment call. + String assessmentId = "account-defender-assessment-id"; + + // hashedAccountId: Set the hashedAccountId corresponding to the assessment id. + ByteString hashedAccountId = ByteString.copyFrom(new byte[] {}); + + annotateAssessment(projectID, assessmentId, hashedAccountId); + } + + /** + * Pre-requisite: Create an assessment before annotating. Annotate an assessment to provide + * feedback on the correctness of recaptcha prediction. + */ + public static void annotateAssessment( + String projectID, String assessmentId, ByteString hashedAccountId) throws IOException { + + try (RecaptchaEnterpriseServiceClient client = RecaptchaEnterpriseServiceClient.create()) { + // Build the annotation request. + // For more info on when/how to annotate, see: + // https://cloud.google.com/recaptcha-enterprise/docs/annotate-assessment#when_to_annotate + AnnotateAssessmentRequest annotateAssessmentRequest = + AnnotateAssessmentRequest.newBuilder() + .setName(AssessmentName.of(projectID, assessmentId).toString()) + .setAnnotation(Annotation.LEGITIMATE) + .addReasons(Reason.PASSED_TWO_FACTOR) + .setHashedAccountId(hashedAccountId) + .build(); + + // Empty response is sent back. + AnnotateAssessmentResponse response = client.annotateAssessment(annotateAssessmentRequest); + System.out.println("Annotated response sent successfully ! " + response); + } + } +} +// [END recaptcha_enterprise_annotate_account_defender_assessment] diff --git a/recaptcha_enterprise/snippets/src/main/java/recaptcha/account_defender/ListRelatedAccountGroupMemberships.java b/recaptcha_enterprise/snippets/src/main/java/recaptcha/account_defender/ListRelatedAccountGroupMemberships.java new file mode 100644 index 00000000000..a8a545d812e --- /dev/null +++ b/recaptcha_enterprise/snippets/src/main/java/recaptcha/account_defender/ListRelatedAccountGroupMemberships.java @@ -0,0 +1,60 @@ +/* + * 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 account_defender; + +// [START recaptcha_enterprise_list_related_account_group_membership] + +import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceClient; +import com.google.recaptchaenterprise.v1.ListRelatedAccountGroupMembershipsRequest; +import com.google.recaptchaenterprise.v1.RelatedAccountGroupMembership; +import java.io.IOException; + +public class ListRelatedAccountGroupMemberships { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + // projectId: Google Cloud Project Id. + String projectId = "project-id"; + + // relatedAccountGroup: Name of the account group. + String relatedAccountGroup = "related-account-group-name"; + + listRelatedAccountGroupMemberships(projectId, relatedAccountGroup); + } + + /** Given a group name, list memberships in the group. */ + public static void listRelatedAccountGroupMemberships( + String projectId, String relatedAccountGroup) throws IOException { + try (RecaptchaEnterpriseServiceClient client = RecaptchaEnterpriseServiceClient.create()) { + + // Construct the request. + ListRelatedAccountGroupMembershipsRequest request = + ListRelatedAccountGroupMembershipsRequest.newBuilder() + .setParent( + String.format( + "projects/%s/relatedaccountgroups/%s", projectId, relatedAccountGroup)) + .build(); + + for (RelatedAccountGroupMembership relatedAccountGroupMembership : + client.listRelatedAccountGroupMemberships(request).iterateAll()) { + System.out.println(relatedAccountGroupMembership.getName()); + } + System.out.println("Finished listing related account group memberships."); + } + } +} +// [END recaptcha_enterprise_list_related_account_group_membership] diff --git a/recaptcha_enterprise/snippets/src/main/java/recaptcha/account_defender/ListRelatedAccountGroups.java b/recaptcha_enterprise/snippets/src/main/java/recaptcha/account_defender/ListRelatedAccountGroups.java new file mode 100644 index 00000000000..841a04d0aa4 --- /dev/null +++ b/recaptcha_enterprise/snippets/src/main/java/recaptcha/account_defender/ListRelatedAccountGroups.java @@ -0,0 +1,50 @@ +/* + * 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 account_defender; + +// [START recaptcha_enterprise_list_related_account_group] + +import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceClient; +import com.google.recaptchaenterprise.v1.ListRelatedAccountGroupsRequest; +import com.google.recaptchaenterprise.v1.RelatedAccountGroup; +import java.io.IOException; + +public class ListRelatedAccountGroups { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + // projectId : Google Cloud Project Id. + String projectId = "project-id"; + + listRelatedAccountGroups(projectId); + } + + // List related account groups in the project. + public static void listRelatedAccountGroups(String projectId) throws IOException { + try (RecaptchaEnterpriseServiceClient client = RecaptchaEnterpriseServiceClient.create()) { + + ListRelatedAccountGroupsRequest request = + ListRelatedAccountGroupsRequest.newBuilder().setParent("projects/" + projectId).build(); + + System.out.println("Listing related account groups.."); + for (RelatedAccountGroup group : client.listRelatedAccountGroups(request).iterateAll()) { + System.out.println(group.getName()); + } + } + } +} +// [END recaptcha_enterprise_list_related_account_group] diff --git a/recaptcha_enterprise/snippets/src/main/java/recaptcha/account_defender/SearchRelatedAccountGroupMemberships.java b/recaptcha_enterprise/snippets/src/main/java/recaptcha/account_defender/SearchRelatedAccountGroupMemberships.java new file mode 100644 index 00000000000..9f55670ef88 --- /dev/null +++ b/recaptcha_enterprise/snippets/src/main/java/recaptcha/account_defender/SearchRelatedAccountGroupMemberships.java @@ -0,0 +1,61 @@ +/* + * 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 account_defender; + +// [START recaptcha_enterprise_search_related_account_group_membership] + +import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceClient; +import com.google.protobuf.ByteString; +import com.google.recaptchaenterprise.v1.RelatedAccountGroupMembership; +import com.google.recaptchaenterprise.v1.SearchRelatedAccountGroupMembershipsRequest; +import java.io.IOException; +import java.security.NoSuchAlgorithmException; + +public class SearchRelatedAccountGroupMemberships { + + public static void main(String[] args) throws IOException, NoSuchAlgorithmException { + // TODO(developer): Replace these variables before running the sample. + // projectId: Google Cloud Project Id. + String projectId = "project-id"; + + // HMAC SHA-256 hashed unique id of the customer. + ByteString hashedAccountId = ByteString.copyFrom(new byte[] {}); + + searchRelatedAccountGroupMemberships(projectId, hashedAccountId); + } + + // List group memberships for the hashed account id. + public static void searchRelatedAccountGroupMemberships( + String projectId, ByteString hashedAccountId) throws IOException { + try (RecaptchaEnterpriseServiceClient client = RecaptchaEnterpriseServiceClient.create()) { + + SearchRelatedAccountGroupMembershipsRequest request = + SearchRelatedAccountGroupMembershipsRequest.newBuilder() + .setParent("projects/" + projectId) + .setHashedAccountId(hashedAccountId) + .build(); + + for (RelatedAccountGroupMembership groupMembership : + client.searchRelatedAccountGroupMemberships(request).iterateAll()) { + System.out.println(groupMembership.getName()); + } + System.out.printf( + "Finished searching related account group memberships for %s!", hashedAccountId); + } + } +} +// [END recaptcha_enterprise_search_related_account_group_membership] diff --git a/recaptcha_enterprise/snippets/src/main/java/recaptcha/passwordleak/CreatePasswordLeakAssessment.java b/recaptcha_enterprise/snippets/src/main/java/recaptcha/passwordleak/CreatePasswordLeakAssessment.java new file mode 100644 index 00000000000..e0caf63a423 --- /dev/null +++ b/recaptcha_enterprise/snippets/src/main/java/recaptcha/passwordleak/CreatePasswordLeakAssessment.java @@ -0,0 +1,215 @@ +/* + * 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 passwordleak; + +// [START recaptcha_enterprise_password_leak_verification] + +import com.google.cloud.recaptcha.passwordcheck.PasswordCheckResult; +import com.google.cloud.recaptcha.passwordcheck.PasswordCheckVerification; +import com.google.cloud.recaptcha.passwordcheck.PasswordCheckVerifier; +import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceClient; +import com.google.protobuf.ByteString; +import com.google.recaptchaenterprise.v1.Assessment; +import com.google.recaptchaenterprise.v1.CreateAssessmentRequest; +import com.google.recaptchaenterprise.v1.Event; +import com.google.recaptchaenterprise.v1.PrivatePasswordLeakVerification; +import com.google.recaptchaenterprise.v1.TokenProperties; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; + +public class CreatePasswordLeakAssessment { + + public static void main(String[] args) + throws IOException, ExecutionException, InterruptedException { + // TODO(developer): Replace these variables before running the sample. + // GCloud Project ID. + String projectID = "project-id"; + + // Site key obtained by registering a domain/app to use recaptcha Enterprise. + String recaptchaSiteKey = "recaptcha-site-key"; + + // The token obtained from the client on passing the recaptchaSiteKey. + // To get the token, integrate the recaptchaSiteKey with frontend. See, + // https://cloud.google.com/recaptcha-enterprise/docs/instrument-web-pages#frontend_integration_score + String token = "recaptcha-token"; + + // Action name corresponding to the token. + String recaptchaAction = "recaptcha-action"; + + checkPasswordLeak(projectID, recaptchaSiteKey, token, recaptchaAction); + } + + /* + * Detect password leaks and breached credentials to prevent account takeovers (ATOs) + * and credential stuffing attacks. + * For more information, see: https://cloud.google.com/recaptcha-enterprise/docs/getting-started + * and https://security.googleblog.com/2019/02/protect-your-accounts-from-data.html + + * Steps: + * 1. Use the 'createVerification' method to hash and Encrypt the hashed username and password. + * 2. Send the hash prefix (2-byte) and the encrypted credentials to create the assessment. + * (Hash prefix is used to partition the database.) + * 3. Password leak assessment returns a database whose prefix matches the sent hash prefix. + * Create Assessment also sends back re-encrypted credentials. + * 4. The re-encrypted credential is then locally verified to see if there is a + * match in the database. + * + * To perform hashing, encryption and verification (steps 1, 2 and 4), + * reCAPTCHA Enterprise provides a helper library in Java. + * See, https://github.com/GoogleCloudPlatform/java-recaptcha-password-check-helpers + + * If you want to extend this behavior to your own implementation/ languages, + * make sure to perform the following steps: + * 1. Hash the credentials (First 2 bytes of the result is the 'lookupHashPrefix') + * 2. Encrypt the hash (result = 'encryptedUserCredentialsHash') + * 3. Get back the PasswordLeak information from reCAPTCHA Enterprise Create Assessment. + * 4. Decrypt the obtained 'credentials.getReencryptedUserCredentialsHash()' + * with the same key you used for encryption. + * 5. Check if the decrypted credentials are present in 'credentials.getEncryptedLeakMatchPrefixesList()'. + * 6. If there is a match, that indicates a credential breach. + */ + public static void checkPasswordLeak( + String projectID, String recaptchaSiteKey, String token, String recaptchaAction) + throws ExecutionException, InterruptedException, IOException { + // Set the username and password to be checked. + String username = "username"; + String password = "password123"; + + // Instantiate the java-password-leak-helper library to perform the cryptographic functions. + PasswordCheckVerifier passwordLeak = new PasswordCheckVerifier(); + + // Create the request to obtain the hash prefix and encrypted credentials. + PasswordCheckVerification verification = + passwordLeak.createVerification(username, password).get(); + + byte[] lookupHashPrefix = verification.getLookupHashPrefix(); + byte[] encryptedUserCredentialsHash = verification.getEncryptedLookupHash(); + + // Pass the credentials to the createPasswordLeakAssessment() to get back + // the matching database entry for the hash prefix. + PrivatePasswordLeakVerification credentials = + createPasswordLeakAssessment( + projectID, + recaptchaSiteKey, + token, + recaptchaAction, + lookupHashPrefix, + encryptedUserCredentialsHash); + + // Convert to appropriate input format. + List leakMatchPrefixes = + credentials.getEncryptedLeakMatchPrefixesList().stream() + .map(ByteString::toByteArray) + .collect(Collectors.toList()); + + // Verify if the encrypted credentials are present in the obtained match list. + PasswordCheckResult result = + passwordLeak + .verify( + verification, + credentials.getReencryptedUserCredentialsHash().toByteArray(), + leakMatchPrefixes) + .get(); + + // Check if the credential is leaked. + boolean isLeaked = result.areCredentialsLeaked(); + System.out.printf("Is Credential leaked: %s", isLeaked); + } + + // Create a reCAPTCHA Enterprise assessment. + // Returns: PrivatePasswordLeakVerification which contains + // reencryptedUserCredentialsHash and credential breach database + // whose prefix matches the lookupHashPrefix. + private static PrivatePasswordLeakVerification createPasswordLeakAssessment( + String projectID, + String recaptchaSiteKey, + String token, + String recaptchaAction, + byte[] lookupHashPrefix, + byte[] encryptedUserCredentialsHash) + throws IOException { + try (RecaptchaEnterpriseServiceClient client = RecaptchaEnterpriseServiceClient.create()) { + + // Set the properties of the event to be tracked. + Event event = Event.newBuilder().setSiteKey(recaptchaSiteKey).setToken(token).build(); + + // Set the hashprefix and credentials hash. + // Setting this will trigger the Password leak protection. + PrivatePasswordLeakVerification passwordLeakVerification = + PrivatePasswordLeakVerification.newBuilder() + .setLookupHashPrefix(ByteString.copyFrom(lookupHashPrefix)) + .setEncryptedUserCredentialsHash(ByteString.copyFrom(encryptedUserCredentialsHash)) + .build(); + + // Build the assessment request. + CreateAssessmentRequest createAssessmentRequest = + CreateAssessmentRequest.newBuilder() + .setParent(String.format("projects/%s", projectID)) + .setAssessment( + Assessment.newBuilder() + .setEvent(event) + // Set request for Password leak verification. + .setPrivatePasswordLeakVerification(passwordLeakVerification) + .build()) + .build(); + + // Send the create assessment request. + Assessment response = client.createAssessment(createAssessmentRequest); + + // Check validity and integrity of the response. + if (!checkTokenIntegrity(response.getTokenProperties(), recaptchaAction)) { + return passwordLeakVerification; + } + + // Get the reCAPTCHA Enterprise score. + float recaptchaScore = response.getRiskAnalysis().getScore(); + System.out.println("The reCAPTCHA score is: " + recaptchaScore); + + // Get the assessment name (id). Use this to annotate the assessment. + String assessmentName = response.getName(); + System.out.println( + "Assessment name: " + assessmentName.substring(assessmentName.lastIndexOf("/") + 1)); + + return response.getPrivatePasswordLeakVerification(); + } + } + + // Check for token validity and action integrity. + private static boolean checkTokenIntegrity( + TokenProperties tokenProperties, String recaptchaAction) { + // Check if the token is valid. + if (!tokenProperties.getValid()) { + System.out.println( + "The Password check call failed because the token was: " + + tokenProperties.getInvalidReason().name()); + return false; + } + + // Check if the expected action was executed. + if (!tokenProperties.getAction().equals(recaptchaAction)) { + System.out.printf( + "The action attribute in the reCAPTCHA tag '%s' does not match " + + "the action '%s' you are expecting to score", + tokenProperties.getAction(), recaptchaAction); + return false; + } + return true; + } +} +// [END recaptcha_enterprise_password_leak_verification] diff --git a/recaptcha_enterprise/snippets/src/pom.xml b/recaptcha_enterprise/snippets/src/pom.xml new file mode 100644 index 00000000000..091ee4905ad --- /dev/null +++ b/recaptcha_enterprise/snippets/src/pom.xml @@ -0,0 +1,129 @@ + + + 4.0.0 + com.example.recaptchaenterprise + recaptcha-enterprise-snippets + jar + Google reCAPTCHA Enterprise Snippets + https://github.com/GoogleCloudPlatform/java-docs-samples/tree/main/recaptchaenterprise + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + 11 + 11 + UTF-8 + + + + + + + com.google.cloud + libraries-bom + 26.1.4 + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + + com.google.cloud + google-cloud-recaptchaenterprise + + + com.google.cloud + recaptcha-password-check-helpers + 1.0.1 + + + + + + org.seleniumhq.selenium + selenium-java + 4.5.0 + + + org.seleniumhq.selenium + selenium-chrome-driver + 4.5.0 + + + com.google.guava + guava + 31.1-jre + + + + io.github.bonigarcia + webdrivermanager + 5.3.0 + + + + + + + + junit + junit + 4.13.2 + test + + + com.google.truth + truth + 1.1.3 + test + + + + + + + + org.springframework.boot + spring-boot-starter-web + 2.7.4 + + + org.springframework.boot + spring-boot-starter-test + 2.7.4 + test + + + org.springframework.boot + spring-boot-starter-thymeleaf + 2.7.4 + + + com.google.api + api-common + + + + + + diff --git a/recaptcha_enterprise/snippets/src/resources/templates/index.html b/recaptcha_enterprise/snippets/src/resources/templates/index.html new file mode 100644 index 00000000000..77e1ed6f659 --- /dev/null +++ b/recaptcha_enterprise/snippets/src/resources/templates/index.html @@ -0,0 +1,79 @@ + + + + + reCAPTCHA-Enterprise + + + + + +

+ + + + + + + +
+
+ +
+ + \ No newline at end of file diff --git a/recaptcha_enterprise/snippets/src/test/java/app/SnippetsIT.java b/recaptcha_enterprise/snippets/src/test/java/app/SnippetsIT.java new file mode 100644 index 00000000000..95958adf933 --- /dev/null +++ b/recaptcha_enterprise/snippets/src/test/java/app/SnippetsIT.java @@ -0,0 +1,351 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package app; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import account_defender.AccountDefenderAssessment; +import account_defender.AnnotateAccountDefenderAssessment; +import account_defender.ListRelatedAccountGroupMemberships; +import account_defender.ListRelatedAccountGroups; +import account_defender.SearchRelatedAccountGroupMemberships; +import com.google.protobuf.ByteString; +import io.github.bonigarcia.wdm.WebDriverManager; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.Duration; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.openqa.selenium.By; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.support.ui.WebDriverWait; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.web.util.UriComponentsBuilder; +import recaptcha.AnnotateAssessment; +import recaptcha.GetMetrics; + +@RunWith(SpringJUnit4ClassRunner.class) +@EnableAutoConfiguration +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +public class SnippetsIT { + + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String DOMAIN_NAME = "localhost"; + private static String RECAPTCHA_SITE_KEY_1 = "recaptcha-site-key1"; + private static String RECAPTCHA_SITE_KEY_2 = "recaptcha-site-key2"; + private static WebDriver browser; + @LocalServerPort private int randomServerPort; + private ByteArrayOutputStream stdOut; + + @Test + public void testCreateAnnotateAssessment() + throws JSONException, IOException, InterruptedException, NoSuchAlgorithmException, + ExecutionException { + // Create an assessment. + String testURL = "http://localhost:" + randomServerPort + "/"; + JSONObject createAssessmentResult = + createAssessment(testURL, ByteString.EMPTY, AssessmentType.ASSESSMENT); + String assessmentName = createAssessmentResult.getString("assessmentName"); + // Verify that the assessment name has been modified post the assessment creation. + assertThat(assessmentName).isNotEmpty(); + + // Annotate the assessment. + AnnotateAssessment.annotateAssessment(PROJECT_ID, assessmentName); + assertThat(stdOut.toString()).contains("Annotated response sent successfully ! "); + } + + // 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, InterruptedException { + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + + // Create reCAPTCHA Site key and associate the given domain. + RECAPTCHA_SITE_KEY_1 = recaptcha.CreateSiteKey.createSiteKey(PROJECT_ID, DOMAIN_NAME); + RECAPTCHA_SITE_KEY_2 = recaptcha.CreateSiteKey.createSiteKey(PROJECT_ID, DOMAIN_NAME); + TimeUnit.SECONDS.sleep(5); + + // Set Selenium Driver to Chrome. + WebDriverManager.chromedriver().setup(); + browser = new ChromeDriver(); + } + + @AfterClass + public static void cleanUp() + throws IOException, ExecutionException, InterruptedException, TimeoutException { + ByteArrayOutputStream stdOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdOut)); + + recaptcha.DeleteSiteKey.deleteSiteKey(PROJECT_ID, RECAPTCHA_SITE_KEY_1); + assertThat(stdOut.toString()).contains("reCAPTCHA Site key successfully deleted !"); + + browser.quit(); + + stdOut.close(); + System.setOut(null); + } + + @Before + public void beforeEach() { + stdOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdOut)); + } + + @After + public void afterEach() { + stdOut = null; + System.setOut(null); + } + + @Test + public void testCreateSiteKey() { + // Test if the recaptcha site key has already been successfully created, as part of the setup. + Assert.assertFalse(RECAPTCHA_SITE_KEY_1.isEmpty()); + } + + @Test + public void testGetKey() + throws IOException, ExecutionException, InterruptedException, TimeoutException { + recaptcha.GetSiteKey.getSiteKey(PROJECT_ID, RECAPTCHA_SITE_KEY_1); + assertThat(stdOut.toString()).contains(RECAPTCHA_SITE_KEY_1); + } + + @Test + public void testListSiteKeys() throws IOException { + recaptcha.ListSiteKeys.listSiteKeys(PROJECT_ID); + assertThat(stdOut.toString()).contains(RECAPTCHA_SITE_KEY_1); + } + + @Test + public void testUpdateSiteKey() + throws IOException, InterruptedException, ExecutionException, TimeoutException { + recaptcha.UpdateSiteKey.updateSiteKey(PROJECT_ID, RECAPTCHA_SITE_KEY_1, DOMAIN_NAME); + assertThat(stdOut.toString()).contains("reCAPTCHA Site key successfully updated !"); + } + + @Test + public void testDeleteSiteKey() + throws IOException, ExecutionException, InterruptedException, TimeoutException { + recaptcha.DeleteSiteKey.deleteSiteKey(PROJECT_ID, RECAPTCHA_SITE_KEY_2); + assertThat(stdOut.toString()).contains("reCAPTCHA Site key successfully deleted !"); + } + + @Test + public void testCreateAnnotateAccountDefender() + throws JSONException, IOException, InterruptedException, NoSuchAlgorithmException, + ExecutionException { + + String testURL = "http://localhost:" + randomServerPort + "/"; + // Create a random SHA-256 Hashed account id. + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hashBytes = + digest.digest( + ("default-" + UUID.randomUUID().toString().split("-")[0]) + .getBytes(StandardCharsets.UTF_8)); + ByteString hashedAccountId = ByteString.copyFrom(hashBytes); + + // Create the assessment. + JSONObject createAssessmentResult = + createAssessment(testURL, hashedAccountId, AssessmentType.ACCOUNT_DEFENDER); + String assessmentName = createAssessmentResult.getString("assessmentName"); + // Verify that the assessment name has been modified post the assessment creation. + assertThat(assessmentName).isNotEmpty(); + + // Annotate the assessment. + AnnotateAccountDefenderAssessment.annotateAssessment( + PROJECT_ID, assessmentName, hashedAccountId); + assertThat(stdOut.toString()).contains("Annotated response sent successfully ! "); + + // NOTE: The below assert statements have no significant effect, + // since reCAPTCHA doesn't generate response. + // To generate response, reCAPTCHA needs a threshold number of unique userIdentifier points + // to cluster results. + // Hence, re-running the test 'n' times is currently out of scope. + + // List related account groups in the project. + ListRelatedAccountGroups.listRelatedAccountGroups(PROJECT_ID); + assertThat(stdOut.toString()).contains("Listing related account groups.."); + + // List related account group memberships. + ListRelatedAccountGroupMemberships.listRelatedAccountGroupMemberships(PROJECT_ID, "legitimate"); + assertThat(stdOut.toString()).contains("Finished listing related account group memberships."); + + // Search related group memberships for a hashed account id. + SearchRelatedAccountGroupMemberships.searchRelatedAccountGroupMemberships( + PROJECT_ID, hashedAccountId); + assertThat(stdOut.toString()) + .contains( + String.format( + "Finished searching related account group memberships for %s", hashedAccountId)); + } + + @Test + public void testGetMetrics() throws IOException { + GetMetrics.getMetrics(PROJECT_ID, RECAPTCHA_SITE_KEY_1); + assertThat(stdOut.toString()) + .contains("Retrieved the bucket count for score based key: " + RECAPTCHA_SITE_KEY_1); + } + + @Test + public void testPasswordLeakAssessment() + throws JSONException, IOException, ExecutionException, InterruptedException { + String testURL = "http://localhost:" + randomServerPort + "/"; + createAssessment(testURL, ByteString.EMPTY, AssessmentType.PASSWORD_LEAK); + assertThat(stdOut.toString()).contains("Is Credential leaked: "); + } + + public JSONObject createAssessment( + String testURL, ByteString hashedAccountId, AssessmentType assessmentType) + throws IOException, JSONException, InterruptedException, ExecutionException { + + // Setup the automated browser test and retrieve the token and action. + JSONObject tokenActionPair = initiateBrowserTest(testURL); + + // Send the token for analysis. The analysis score ranges from 0.0 to 1.0 + switch (assessmentType) { + case ACCOUNT_DEFENDER: + { + AccountDefenderAssessment.accountDefenderAssessment( + PROJECT_ID, + RECAPTCHA_SITE_KEY_1, + tokenActionPair.getString("token"), + tokenActionPair.getString("action"), + hashedAccountId); + break; + } + case ASSESSMENT: + { + recaptcha.CreateAssessment.createAssessment( + PROJECT_ID, + RECAPTCHA_SITE_KEY_1, + tokenActionPair.getString("token"), + tokenActionPair.getString("action")); + break; + } + case PASSWORD_LEAK: + { + passwordleak.CreatePasswordLeakAssessment.checkPasswordLeak( + PROJECT_ID, + RECAPTCHA_SITE_KEY_1, + tokenActionPair.getString("token"), + tokenActionPair.getString("action")); + break; + } + } + + // Assert the response. + String response = stdOut.toString(); + assertThat(response).contains("Assessment name: "); + assertThat(response).contains("The reCAPTCHA score is: "); + if (!hashedAccountId.isEmpty()) { + assertThat(response).contains("Account Defender Assessment Result: "); + } + + // Retrieve the results. + float recaptchaScore = 0; + String assessmentName = ""; + for (String line : response.split("\n")) { + if (line.contains("The reCAPTCHA score is: ")) { + recaptchaScore = Float.parseFloat(substr(line)); + } else if (line.contains("Assessment name: ")) { + assessmentName = substr(line); + } + } + + // Set the score. + browser.findElement(By.cssSelector("#assessment")).sendKeys(String.valueOf(recaptchaScore)); + return new JSONObject() + .put("recaptchaScore", recaptchaScore) + .put("assessmentName", assessmentName); + } + + enum AssessmentType { + ASSESSMENT, + ACCOUNT_DEFENDER, + PASSWORD_LEAK; + + AssessmentType() {} + } + + public JSONObject initiateBrowserTest(String testURL) + throws IOException, JSONException, InterruptedException { + // Construct the URL to call for validating the assessment. + URI url = + UriComponentsBuilder.fromUriString(testURL) + .queryParam("recaptchaSiteKey", RECAPTCHA_SITE_KEY_1) + .build() + .encode() + .toUri(); + + browser.get(url.toURL().toString()); + + // Wait until the page is loaded. + JavascriptExecutor js = (JavascriptExecutor) browser; + new WebDriverWait(browser, Duration.ofSeconds(10)) + .until(webDriver -> js.executeScript("return document.readyState").equals("complete")); + + // Pass the values to be entered. + browser.findElement(By.id("username")).sendKeys("username"); + browser.findElement(By.id("password")).sendKeys("password"); + + // On clicking the button, the request params will be sent to reCAPTCHA. + browser.findElement(By.id("recaptchabutton")).click(); + + TimeUnit.SECONDS.sleep(1); + + // Retrieve the reCAPTCHA token response. + WebElement element = browser.findElement(By.cssSelector("#assessment")); + String token = element.getAttribute("data-token"); + String action = element.getAttribute("data-action"); + + return new JSONObject().put("token", token).put("action", action); + } + + public String substr(String line) { + return line.substring((line.lastIndexOf(":") + 1)).trim(); + } +} diff --git a/retail/interactive-tutorials/README.md b/retail/interactive-tutorials/README.md new file mode 100644 index 00000000000..1877f2aca05 --- /dev/null +++ b/retail/interactive-tutorials/README.md @@ -0,0 +1,211 @@ +# Retail Search Interactive Tutorials + +## Run tutorials in Cloud Shell + +To advance with the interactive tutorials, use Retail Search step-by-step manuals on the right side of the Cloud Shell IDE: +![Interactive tutorials](images/tutorial1.png) + +The interactive tutorial should open by default. If it didn’t, click on the Tutorial symbol in the menu bar to open the step-by-step manual: +![Toggle tutorial](images/tutorials2.png) + +For more details about the Cloud Shell environment, refer to the [Cloud Shell documentation](https://cloud.google.com/shell/docs). + +## Interactive tutorial flow + +Interactive guides are intended to help you understand the features provided by Google Cloud Retail Search and test the Retail API in action. + +To proceed with the tutorial, choose a language you’ll be deploying your project in: +![Select a programming language](images/tutorials3.png) + + +To begin with the tutorial workflow, click the Start button: +![Begin with the tutorial](images/tutorials4.png) + +Then, you can use Next and Previous buttons to navigate the tutorial pages. + +## Java code samples + +The code here demonstrates how to consume Google Retail Search API in Java + +## Get started with the Google Cloud Retail API + +The Retail API provides you with the following possibilities to: + - Create and maintaining the catalog data. + - Fine-tune the search configuration. + - Import and maintain the user events data. + +You can find the information about the Retail services in the [documentation](https://cloud.google.com/retail/docs) + +If you would like to have a closer look at the Retail API features and try them yourself, +the best option is to use the [Interactive Tutorials](https://cloud.google.com/retail/docs/overview). In the documentation chapters find the "Guide me" button, the tutorials will be launched in the CloudShell environment, and you will be able to request the Retail services and check the response with minimum time and effort. + +The code samples in the directory **java-docs-samples/retail/interactive-tutorials** are explicitly created for use with the Retail Interactive Tutorials. + +If, for some reason, you have decided to proceed with these code samples without the tutorial, please go through the following steps and set up the required preconditions. + +## Prepare your work environment + +To prepare the work environment you should perform the following steps: +- Create a service account. +- Create a service account key and set it to authorize your calls to the Retail API. +- Install Google Cloud Retail library. + +### There are two ways to set up your work environment: + +- If you want to **speed up the process** of setting up the working environment, run the script java-docs-samples/retail/interactive-tutorials/src/main/java/user_environment_setup.sh and skip the next **set up the work environment step-by-step** tutorial step: + + ```bash + bash java-docs-samples/retail/interactive-tutorials/user_environment_setup.sh + ``` + +- If you want to perform the environment set up step by step along with getting the explanation you should proceed with the next tutorial step. + +## Set up the work environment step-by-step + +### Create service account + +To access the Retail API, you must create a service account. Check that you are an owner of your Google Cloud project on the [IAM page](https://console.cloud.google.com/iam-admin/iam). + +1. To create a service account, perform the following command: + + ```bash + gcloud iam service-accounts create + ``` + +1. Assign the needed roles to your service account: + + ```bash + for role in {retail.admin,editor,bigquery.admin} + do gcloud projects add-iam-policy-binding --member="serviceAccount:@.iam.gserviceaccount.com" --role="roles/${role}" + done + ``` + +1. Use the following command to show the service account email: + + ```bash + gcloud iam service-accounts list|grep + ``` + + Copy the service account email. + + +1. Upload your service account key JSON file and use it to activate the service + account: + + ```bash + gcloud iam service-accounts keys create ~/key.json --iam-account + ``` + + ```bash + gcloud auth activate-service-account --key-file ~/key.json + ``` + +1. Set the key as the GOOGLE_APPLICATION_CREDENTIALS environment variable to + use it for sending requests to the Retail API. + + ```bash + export GOOGLE_APPLICATION_CREDENTIALS=~/key.json + ``` + +### Google Cloud Retail libraries + +Learn more about the [Java Google Cloud Retail library](https://googleapis.dev/java/google-cloud-retail/latest/index.html). + +## Congrats! You have configured your work environment + +1. Check that you are in the directory with code samples. + + The code samples for each of the Retail services are stored in different directories. + + Go to the code samples directory, your starting point to run more commands. + + ```bash + cd java-docs-samples/retail/interactive-tutorials/ + ``` + +## Import catalog data + +This step is required if this is the first Retail API tutorial that you run. +Otherwise, you can skip it. + +There is a java-docs-samples/retail/interactive-tutorials/src/main/resources/products.json file with valid products prepared in the `resources` directory. + +The other file, java-docs-samples/retail/interactive-tutorials/src/main/resources/products_some_invalid.json, contains both valid and invalid products. You will use it to check the error handling. + +- If you want to **speed up the process**, run the following script in the Terminal directory to import all products to catalog and skip the next **Prepare the catalog data step-by-step** tutorial step: + + ```bash + bash java-docs-samples/retail/interactive-tutorials/user_import_data_to_catalog.sh + ``` + +- If you want to upload products to the catalog step by step along with getting the explanation, you should proceed with the next tutorial step. + +## Prepare the catalog data step-by-step + +### Upload catalog data to Cloud Storage + +In your own project you need to create a Cloud Storage bucket and put the JSON file there. +The bucket name must be unique. For convenience, you can name it `_`. + +1. The code samples for each of the Retail services are stored in different directories. + + Go to the code samples directory, your starting point to run more commands. + + ```bash + cd java-docs-samples/retail/interactive-tutorials + ``` + +1. To create the bucket and upload the JSON file, open java-docs-samples/retail/interactive-tutorials/src/main/java/product/setup/ProductsCreateGcsBucket.java file + +1. Go to the **product** directory and run the following command in the Terminal: + + ```bash + mvn compile exec:java + -Dexec.mainClass=product.setup.ProductsCreateGcsBucket + ``` + + Now you can see the bucket is created in the [Cloud Storage](https://console.cloud.google.com/storage/browser), and the files are uploaded. + +1. The name of the created Cloud Storage bucket is shown in the Terminal. + + ``` + The gcs bucket _ was created + ``` + + Copy the name and set it as the environment variable `BUCKET_NAME`: + + ```bash + export BUCKET_NAME= + ``` + +### Import products to the Retail Catalog + +To import the prepared products to a catalog, open java-docs-samples/retail/interactive-tutorials/src/main/java/product/ImportProductsGcs.java file and run the following command in the Terminal: + +```bash +mvn compile exec:java -Dexec.mainClass=product.ImportProductsGcs +``` + +## Your Retail catalog is ready to use! + +### Running code samples + +Use maven command to run specific code sample: + +``` +mvn compile exec:java -Dexec.mainClass="package.CodeSampleClass" +``` + +### Running unit tests + +Use maven command to run specific unit test class: + +``` +mvn test -Dtest=TestClassName +``` + +Use maven command to run all unit tests: + +``` +mvn test +``` diff --git a/retail/interactive-tutorials/images/tutorail1.img b/retail/interactive-tutorials/images/tutorail1.img new file mode 100644 index 00000000000..e69de29bb2d diff --git a/retail/interactive-tutorials/images/tutorial1.png b/retail/interactive-tutorials/images/tutorial1.png new file mode 100644 index 00000000000..edeea8376c2 Binary files /dev/null and b/retail/interactive-tutorials/images/tutorial1.png differ diff --git a/retail/interactive-tutorials/images/tutorials2.png b/retail/interactive-tutorials/images/tutorials2.png new file mode 100644 index 00000000000..3321a0de373 Binary files /dev/null and b/retail/interactive-tutorials/images/tutorials2.png differ diff --git a/retail/interactive-tutorials/images/tutorials3.png b/retail/interactive-tutorials/images/tutorials3.png new file mode 100644 index 00000000000..ae7518f01e8 Binary files /dev/null and b/retail/interactive-tutorials/images/tutorials3.png differ diff --git a/retail/interactive-tutorials/images/tutorials4.png b/retail/interactive-tutorials/images/tutorials4.png new file mode 100644 index 00000000000..f649a9b3a38 Binary files /dev/null and b/retail/interactive-tutorials/images/tutorials4.png differ diff --git a/retail/interactive-tutorials/pom.xml b/retail/interactive-tutorials/pom.xml new file mode 100644 index 00000000000..44a5a588713 --- /dev/null +++ b/retail/interactive-tutorials/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + com.example.retail + retail-interactive-tutorials + jar + Google Cloud Retail Interactive Tutorials + https://github.com/GoogleCloudPlatform/java-docs-samples/tree/main/retail + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + + 1.8 + 1.8 + UTF-8 + + + + + + com.google.cloud + libraries-bom + 26.1.3 + pom + import + + + + + + + com.google.cloud + google-cloud-retail + 2.5.2 + + + junit + junit + 4.13.2 + test + + + com.google.cloud + google-cloud-bigquery + 2.17.0 + + + com.google.cloud + google-cloud-storage + 2.13.0 + + + com.google.code.gson + gson + 2.9.1 + + + com.google.truth + truth + 1.1.3 + test + + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + true + + + + + diff --git a/retail/interactive-tutorials/src/main/java/events/ImportUserEventsBigQuery.java b/retail/interactive-tutorials/src/main/java/events/ImportUserEventsBigQuery.java new file mode 100644 index 00000000000..4f66b047409 --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/events/ImportUserEventsBigQuery.java @@ -0,0 +1,124 @@ +/* + * 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. + */ + +/* + * Import user events into a catalog from GCS using Retail API + */ + +package events; + +import com.google.api.gax.rpc.NotFoundException; +import com.google.cloud.ServiceOptions; +import com.google.cloud.bigquery.BigQueryException; +import com.google.cloud.retail.v2.BigQuerySource; +import com.google.cloud.retail.v2.ImportMetadata; +import com.google.cloud.retail.v2.ImportUserEventsRequest; +import com.google.cloud.retail.v2.ImportUserEventsResponse; +import com.google.cloud.retail.v2.UserEventInputConfig; +import com.google.cloud.retail.v2.UserEventServiceClient; +import com.google.longrunning.Operation; +import com.google.longrunning.OperationsClient; +import java.io.IOException; +import java.time.Instant; +import java.util.concurrent.TimeUnit; + +public class ImportUserEventsBigQuery { + + public static void main(String[] args) throws IOException, InterruptedException { + // TODO(developer): Set projectId to your Google Cloud Platform project ID. + String projectId = ServiceOptions.getDefaultProjectId(); + String defaultCatalog = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + // To check error handling use invalid catalog name here: + // defaultCatalog = "invalid_catalog_name"; + + // TODO(developer): Set datasetId to your datasetId + String datasetId = "user_events"; + // TODO(developer): Set tableId to your tableId + String tableId = "events"; + // To check error handling use table of invalid user events here: + // tableId = "events_some_invalid" + + importUserEventsFromBigQuery(projectId, defaultCatalog, datasetId, tableId); + } + + public static void importUserEventsFromBigQuery( + String projectId, String defaultCatalog, String datasetId, String tableId) + throws IOException, InterruptedException { + + try { + String dataSchema = "user_event"; + + BigQuerySource bigQuerySource = + BigQuerySource.newBuilder() + .setProjectId(projectId) + .setDatasetId(datasetId) + .setTableId(tableId) + .setDataSchema(dataSchema) + .build(); + + UserEventInputConfig inputConfig = + UserEventInputConfig.newBuilder().setBigQuerySource(bigQuerySource).build(); + + ImportUserEventsRequest importRequest = + ImportUserEventsRequest.newBuilder() + .setParent(defaultCatalog) + .setInputConfig(inputConfig) + .build(); + + System.out.printf("Import user events from BigQuery source request: %s%n", importRequest); + + // 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 (UserEventServiceClient serviceClient = UserEventServiceClient.create()) { + String operationName = + serviceClient.importUserEventsCallable().call(importRequest).getName(); + + System.out.printf("OperationName = %s%n", operationName); + OperationsClient operationsClient = serviceClient.getOperationsClient(); + Operation operation = operationsClient.getOperation(operationName); + + Instant deadline = Instant.now().plusSeconds(60); + + while (!operation.getDone() || Instant.now().isBefore(deadline)) { + // Keep polling the operation periodically until the import task is done. + TimeUnit.SECONDS.sleep(30); + operation = operationsClient.getOperation(operationName); + } + + if (operation.hasMetadata()) { + ImportMetadata metadata = operation.getMetadata().unpack(ImportMetadata.class); + System.out.printf( + "Number of successfully imported events: %s%n", metadata.getSuccessCount()); + System.out.printf( + "Number of failures during the importing: %s%n", metadata.getFailureCount()); + } + + if (operation.hasResponse()) { + ImportUserEventsResponse response = + operation.getResponse().unpack(ImportUserEventsResponse.class); + System.out.printf("Operation result: %s%n", response); + } + } + } catch (BigQueryException e) { + System.out.printf("Exception message: %s", e.getMessage()); + } catch (NotFoundException e) { + System.out.printf("Catalog name is not found.%n%s%n", e.getMessage()); + } + } +} diff --git a/retail/interactive-tutorials/src/main/java/events/ImportUserEventsGcs.java b/retail/interactive-tutorials/src/main/java/events/ImportUserEventsGcs.java new file mode 100644 index 00000000000..3128f9ba064 --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/events/ImportUserEventsGcs.java @@ -0,0 +1,130 @@ +/* + * 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. + */ + +/* + * Import user events into a catalog from GCS using Retail API + */ + +package events; + +import com.google.api.gax.rpc.InvalidArgumentException; +import com.google.api.gax.rpc.PermissionDeniedException; +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.GcsSource; +import com.google.cloud.retail.v2.ImportErrorsConfig; +import com.google.cloud.retail.v2.ImportMetadata; +import com.google.cloud.retail.v2.ImportUserEventsRequest; +import com.google.cloud.retail.v2.ImportUserEventsResponse; +import com.google.cloud.retail.v2.UserEventInputConfig; +import com.google.cloud.retail.v2.UserEventServiceClient; +import com.google.longrunning.Operation; +import com.google.longrunning.OperationsClient; +import java.io.IOException; +import java.time.Instant; +import java.util.concurrent.TimeUnit; + +public class ImportUserEventsGcs { + + public static void main(String[] args) throws IOException, InterruptedException { + // TODO(developer): Replace these variables before running the sample. + String projectId = ServiceOptions.getDefaultProjectId(); + String defaultCatalog = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + // TO CHECK ERROR HANDLING PASTE THE INVALID CATALOG NAME HERE: + // defaultCatalog = "invalid_catalog_name"; + String bucketName = System.getenv("EVENTS_BUCKET_NAME"); + String gcsUserEventsObject = "user_events.json"; + // TO CHECK ERROR HANDLING USE THE JSON WITH INVALID USER EVENT: + // gcsUserEventsObject = "user_events_some_invalid.json"; + + importUserEventsFromGcs(defaultCatalog, bucketName, gcsUserEventsObject); + } + + public static void importUserEventsFromGcs( + String defaultCatalog, String bucketName, String gcsUserEventsObject) + throws IOException, InterruptedException { + String gcsBucket = String.format("gs://%s", bucketName); + String gcsErrorsBucket = String.format("%s/error", gcsBucket); + + GcsSource gcsSource = + GcsSource.newBuilder() + .addInputUris(String.format("%s/%s", gcsBucket, gcsUserEventsObject)) + .build(); + + UserEventInputConfig inputConfig = + UserEventInputConfig.newBuilder().setGcsSource(gcsSource).build(); + + System.out.println("GCS source: " + gcsSource.getInputUrisList()); + + ImportErrorsConfig errorsConfig = + ImportErrorsConfig.newBuilder().setGcsPrefix(gcsErrorsBucket).build(); + + ImportUserEventsRequest importRequest = + ImportUserEventsRequest.newBuilder() + .setParent(defaultCatalog) + .setInputConfig(inputConfig) + .setErrorsConfig(errorsConfig) + .build(); + System.out.printf("Import user events from google cloud source request: %s%n", importRequest); + + // 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 (UserEventServiceClient serviceClient = UserEventServiceClient.create()) { + String operationName = serviceClient.importUserEventsCallable().call(importRequest).getName(); + + System.out.println("The operation was started."); + System.out.printf("OperationName = %s%n", operationName); + + OperationsClient operationsClient = serviceClient.getOperationsClient(); + Operation operation = operationsClient.getOperation(operationName); + + Instant deadline = Instant.now().plusSeconds(60); + + while (!operation.getDone() || Instant.now().isBefore(deadline)) { + System.out.println("Please wait till operation is done."); + TimeUnit.SECONDS.sleep(30); + operation = operationsClient.getOperation(operationName); + } + + if (operation.hasMetadata()) { + ImportMetadata metadata = operation.getMetadata().unpack(ImportMetadata.class); + System.out.printf( + "Number of successfully imported events: %s%n", metadata.getSuccessCount()); + System.out.printf( + "Number of failures during the importing: %s%n", metadata.getFailureCount()); + } else { + System.out.println("Metadata is empty."); + } + + if (operation.hasResponse()) { + ImportUserEventsResponse response = + operation.getResponse().unpack(ImportUserEventsResponse.class); + System.out.printf("Operation result: %s%n", response); + } else { + System.out.println("Operation result is empty."); + } + } catch (InvalidArgumentException e) { + System.out.printf( + "%s%n'%s' file does not exist in the bucket. Please " + + "make sure you have followed the setting up instructions.", + e.getMessage(), gcsUserEventsObject); + } catch (PermissionDeniedException e) { + System.out.println(e.getMessage()); + } + } +} diff --git a/retail/interactive-tutorials/src/main/java/events/ImportUserEventsInline.java b/retail/interactive-tutorials/src/main/java/events/ImportUserEventsInline.java new file mode 100644 index 00000000000..d7e882292de --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/events/ImportUserEventsInline.java @@ -0,0 +1,126 @@ +/* + * 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. + */ + +/* + * Import user events into a catalog from inline source using Retail API + */ + +package events; + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.ServiceOptions; +import com.google.cloud.bigquery.BigQueryException; +import com.google.cloud.retail.v2.ImportMetadata; +import com.google.cloud.retail.v2.ImportUserEventsRequest; +import com.google.cloud.retail.v2.ImportUserEventsResponse; +import com.google.cloud.retail.v2.UserEvent; +import com.google.cloud.retail.v2.UserEventInlineSource; +import com.google.cloud.retail.v2.UserEventInputConfig; +import com.google.cloud.retail.v2.UserEventServiceClient; +import com.google.protobuf.Timestamp; +import java.io.IOException; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +public class ImportUserEventsInline { + + public static void main(String[] args) + throws IOException, ExecutionException, InterruptedException { + // TODO(developer): Replace these variables before running the sample. + String projectId = ServiceOptions.getDefaultProjectId(); + String defaultCatalog = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + + importUserEventsFromInlineSource(defaultCatalog); + } + + public static void importUserEventsFromInlineSource(String defaultCatalog) + throws IOException, ExecutionException, InterruptedException { + try { + int userEventsNumber = 3; + int awaitDuration = 30; + List userEvents = new ArrayList<>(); + + for (int i = 0; i < userEventsNumber; i++) { + Instant time = Instant.now(); + Timestamp timestamp = Timestamp.newBuilder().setSeconds(time.getEpochSecond()).build(); + + UserEvent userEvent = + UserEvent.newBuilder() + .setEventType("home-page-view") + .setVisitorId(UUID.randomUUID().toString()) + .setEventTime(timestamp) + .build(); + + userEvents.add(userEvent); + + System.out.printf("User Event: %s%n", i); + System.out.println(userEvent); + } + + UserEventInlineSource inlineSource = + UserEventInlineSource.newBuilder().addAllUserEvents(userEvents).build(); + + UserEventInputConfig inputConfig = + UserEventInputConfig.newBuilder().setUserEventInlineSource(inlineSource).build(); + + ImportUserEventsRequest importRequest = + ImportUserEventsRequest.newBuilder() + .setParent(defaultCatalog) + .setInputConfig(inputConfig) + .build(); + System.out.printf("Import user events from inline source request: %s%n", importRequest); + + // 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 (UserEventServiceClient userEventServiceClient = UserEventServiceClient.create()) { + OperationFuture importOperation = + userEventServiceClient.importUserEventsAsync(importRequest); + + System.out.printf("The operation was started: %s%n", importOperation.getName()); + System.out.println("Please wait till operation is done."); + + userEventServiceClient.awaitTermination(awaitDuration, TimeUnit.SECONDS); + System.out.println("Import user events operation is done."); + + if (importOperation.getMetadata().get() != null) { + System.out.printf( + "Number of successfully imported events: %s%n", + importOperation.getMetadata().get().getSuccessCount()); + + System.out.printf( + "Number of failures during the importing: %s%n", + importOperation.getMetadata().get().getFailureCount()); + } else { + System.out.println("Metadata in bigQuery operation is empty."); + } + if (importOperation.get() != null) { + System.out.printf("Operation result: %s%n", importOperation.get()); + } else { + System.out.println("Operation result is empty."); + } + } + } catch (BigQueryException e) { + System.out.printf("Exception message: %s", e.getMessage()); + } + } +} diff --git a/retail/interactive-tutorials/src/main/java/events/PurgeUserEvent.java b/retail/interactive-tutorials/src/main/java/events/PurgeUserEvent.java new file mode 100644 index 00000000000..c101110437f --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/events/PurgeUserEvent.java @@ -0,0 +1,75 @@ +/* + * 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. + */ + +/* + * Deleting user event using Retail API. + */ + +package events; + +import static setup.SetupCleanup.writeUserEvent; + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.PurgeMetadata; +import com.google.cloud.retail.v2.PurgeUserEventsRequest; +import com.google.cloud.retail.v2.PurgeUserEventsResponse; +import com.google.cloud.retail.v2.UserEventServiceClient; +import java.io.IOException; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +public class PurgeUserEvent { + + public static void main(String[] args) + throws IOException, ExecutionException, InterruptedException { + // TODO(developer): Set projectId to your Google Cloud Platform project ID. + String projectId = ServiceOptions.getDefaultProjectId(); + String defaultCatalog = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + // visitorId is generated randomly + String visitorId = UUID.randomUUID().toString(); + + callPurgeUserEvents(defaultCatalog, visitorId); + } + + public static void callPurgeUserEvents(String defaultCatalog, String visitorId) + throws IOException, ExecutionException, InterruptedException { + writeUserEvent(visitorId); + + // 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 (UserEventServiceClient userEventServiceClient = UserEventServiceClient.create()) { + PurgeUserEventsRequest purgeUserEventsRequest = + PurgeUserEventsRequest.newBuilder() + // To check error handling set invalid filter here: + .setFilter(String.format("visitorId=\"%s\"", visitorId)) + .setParent(defaultCatalog) + // Setting the force field to true deletes user events. If set to false will return + // number of events to be deleted without actually deleting them + .setForce(true) + .build(); + System.out.printf("Purge user events request: %s%n", purgeUserEventsRequest); + + OperationFuture purgeOperation = + userEventServiceClient.purgeUserEventsAsync(purgeUserEventsRequest); + + System.out.printf("The purge operation was started: %s%n", purgeOperation.getName()); + } + } +} diff --git a/retail/interactive-tutorials/src/main/java/events/RejoinUserEvent.java b/retail/interactive-tutorials/src/main/java/events/RejoinUserEvent.java new file mode 100644 index 00000000000..ef624d3cb4e --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/events/RejoinUserEvent.java @@ -0,0 +1,75 @@ +/* + * 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. + */ + +/* + * Starts a user event rejoin operation using Retail API. + */ + +package events; + +import static setup.SetupCleanup.purgeUserEvent; +import static setup.SetupCleanup.writeUserEvent; + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.RejoinUserEventsMetadata; +import com.google.cloud.retail.v2.RejoinUserEventsRequest; +import com.google.cloud.retail.v2.RejoinUserEventsRequest.UserEventRejoinScope; +import com.google.cloud.retail.v2.RejoinUserEventsResponse; +import com.google.cloud.retail.v2.UserEventServiceClient; +import java.io.IOException; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +public class RejoinUserEvent { + + public static void main(String[] args) + throws IOException, ExecutionException, InterruptedException { + // TODO(developer): Set projectId to your Google Cloud Platform project ID. + String projectId = ServiceOptions.getDefaultProjectId(); + String defaultCatalog = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + // visitorId is generated randomly + String visitorId = UUID.randomUUID().toString(); + + callRejoinUserEvents(defaultCatalog, visitorId); + } + + public static void callRejoinUserEvents(String defaultCatalog, String visitorId) + throws IOException, ExecutionException, InterruptedException { + writeUserEvent(visitorId); + + // 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 (UserEventServiceClient userEventServiceClient = UserEventServiceClient.create()) { + RejoinUserEventsRequest rejoinUserEventsRequest = + RejoinUserEventsRequest.newBuilder() + .setParent(defaultCatalog) + .setUserEventRejoinScope(UserEventRejoinScope.UNJOINED_EVENTS) + .build(); + System.out.printf("Rejoin user events request: %s%n", rejoinUserEventsRequest); + + OperationFuture rejoinOperation = + userEventServiceClient.rejoinUserEventsAsync(rejoinUserEventsRequest); + + System.out.printf("The rejoin operation was started: %s%n", rejoinOperation.getName()); + } + + purgeUserEvent(visitorId); + } +} diff --git a/retail/interactive-tutorials/src/main/java/events/WriteUserEvent.java b/retail/interactive-tutorials/src/main/java/events/WriteUserEvent.java new file mode 100644 index 00000000000..b95dd710e8e --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/events/WriteUserEvent.java @@ -0,0 +1,80 @@ +/* + * 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. + */ + +/* + * Write user event using Retail API. + */ + +package events; + +import static setup.SetupCleanup.purgeUserEvent; + +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.UserEvent; +import com.google.cloud.retail.v2.UserEventServiceClient; +import com.google.cloud.retail.v2.WriteUserEventRequest; +import com.google.protobuf.Timestamp; +import java.io.IOException; +import java.time.Instant; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +public class WriteUserEvent { + + public static void main(String[] args) + throws IOException, ExecutionException, InterruptedException { + // TODO(developer): Set projectId to your Google Cloud Platform project ID. + String projectId = ServiceOptions.getDefaultProjectId(); + String defaultCatalog = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + // visitorId is generated randomly + String visitorId = UUID.randomUUID().toString(); + + writeUserEvent(defaultCatalog, visitorId); + } + + public static void writeUserEvent(String defaultCatalog, String visitorId) + 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 (UserEventServiceClient userEventServiceClient = UserEventServiceClient.create()) { + Timestamp timestamp = + Timestamp.newBuilder().setSeconds(Instant.now().getEpochSecond()).build(); + + UserEvent userEvent = + UserEvent.newBuilder() + .setEventType("home-page-view") + .setVisitorId(visitorId) + .setEventTime(timestamp) + .build(); + System.out.println(userEvent); + + WriteUserEventRequest writeUserEventRequest = + WriteUserEventRequest.newBuilder() + .setUserEvent(userEvent) + .setParent(defaultCatalog) + .build(); + System.out.printf("Write user event request: %s%n", writeUserEventRequest); + + userEventServiceClient.writeUserEvent(writeUserEventRequest); + System.out.printf("Written user event: %s%n", userEvent); + } + + purgeUserEvent(visitorId); + } +} diff --git a/retail/interactive-tutorials/src/main/java/events/setup/EventsCreateBigQueryTable.java b/retail/interactive-tutorials/src/main/java/events/setup/EventsCreateBigQueryTable.java new file mode 100644 index 00000000000..f0cf45345ba --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/events/setup/EventsCreateBigQueryTable.java @@ -0,0 +1,57 @@ +/* + * 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 events.setup; + +import static setup.SetupCleanup.createBqDataset; +import static setup.SetupCleanup.createBqTable; +import static setup.SetupCleanup.getGson; +import static setup.SetupCleanup.uploadDataToBqTable; + +import com.google.cloud.bigquery.Field; +import com.google.cloud.bigquery.Schema; +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.stream.Collectors; +import product.setup.ProductsCreateBigqueryTable; + +public class EventsCreateBigQueryTable { + + public static void main(String[] args) throws IOException { + final String dataset = "user_events"; + final String validEventsTable = "events"; + final String invalidEventsTable = "events_some_invalid"; + final String eventsSchemaFilePath = "src/main/resources/events_schema.json"; + // user_events.json and user_events_some_invalid.json are located in the resources folder + final String validEventsSourceFile = + ProductsCreateBigqueryTable.class.getResource("/user_events.json").getPath(); + final String invalidEventsSourceFile = + ProductsCreateBigqueryTable.class.getResource("/user_events_some_invalid.json").getPath(); + + BufferedReader bufferedReader = new BufferedReader(new FileReader(eventsSchemaFilePath)); + String jsonToString = bufferedReader.lines().collect(Collectors.joining()); + jsonToString = jsonToString.replace("\"fields\"", "\"subFields\""); + Field[] fields = getGson().fromJson(jsonToString, Field[].class); + Schema eventsSchema = Schema.of(fields); + + createBqDataset(dataset); + createBqTable(dataset, validEventsTable, eventsSchema); + uploadDataToBqTable(dataset, validEventsTable, validEventsSourceFile); + createBqTable(dataset, invalidEventsTable, eventsSchema); + uploadDataToBqTable(dataset, invalidEventsTable, invalidEventsSourceFile); + } +} diff --git a/retail/interactive-tutorials/src/main/java/events/setup/EventsCreateGcsBucket.java b/retail/interactive-tutorials/src/main/java/events/setup/EventsCreateGcsBucket.java new file mode 100644 index 00000000000..33a2351eb17 --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/events/setup/EventsCreateGcsBucket.java @@ -0,0 +1,58 @@ +/* + * 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 events.setup; + +import static setup.SetupCleanup.createBucket; +import static setup.SetupCleanup.uploadObject; + +import com.google.cloud.ServiceOptions; +import com.google.protobuf.Timestamp; +import java.io.IOException; +import java.time.Instant; + +public class EventsCreateGcsBucket { + + private static final String PROJECT_ID = ServiceOptions.getDefaultProjectId(); + + private static final Timestamp CURRENT_DATE = + Timestamp.newBuilder() + .setSeconds(Instant.now().getEpochSecond()) + .setNanos(Instant.now().getNano()) + .build(); + + private static final String BUCKET_NAME = + String.format("%s_events_%s", PROJECT_ID, CURRENT_DATE.getSeconds()); + + public static void main(String... args) throws IOException { + createBucket(BUCKET_NAME); + System.out.printf("Events gcs bucket %s was created.", BUCKET_NAME); + + uploadObject(BUCKET_NAME, "user_events.json", "src/main/resources/user_events.json"); + System.out.printf("File 'user_events.json' was uploaded into bucket '%s'.", BUCKET_NAME); + + uploadObject( + BUCKET_NAME, + "user_events_some_invalid.json", + "src/main/resources/user_events_some_invalid.json"); + System.out.printf( + "File 'user_events_some_invalid.json' was uploaded into bucket '%s'.", BUCKET_NAME); + } + + public static String getBucketName() { + return BUCKET_NAME; + } +} diff --git a/retail/interactive-tutorials/src/main/java/events/setup/RemoveEventsResources.java b/retail/interactive-tutorials/src/main/java/events/setup/RemoveEventsResources.java new file mode 100644 index 00000000000..de6d43a2091 --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/events/setup/RemoveEventsResources.java @@ -0,0 +1,89 @@ +/* + * 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 events.setup; + +import static setup.SetupCleanup.deleteBucket; +import static setup.SetupCleanup.deleteDataset; + +import com.google.api.gax.rpc.PermissionDeniedException; +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.DeleteProductRequest; +import com.google.cloud.retail.v2.ListProductsRequest; +import com.google.cloud.retail.v2.Product; +import com.google.cloud.retail.v2.ProductServiceClient; +import com.google.cloud.retail.v2.ProductServiceClient.ListProductsPagedResponse; +import com.google.cloud.retail.v2.PurgeUserEventsRequest; +import com.google.cloud.retail.v2.UserEventServiceClient; +import java.io.IOException; + +public class RemoveEventsResources { + + public static void main(String[] args) throws IOException { + String projectId = ServiceOptions.getDefaultProjectId(); + String bucketName = System.getenv("EVENTS_BUCKET_NAME"); + String branchName = + String.format( + "projects/%s/locations/global/catalogs/default_catalog/branches/0", projectId); + + deleteBucket(bucketName); + deleteAllEvents(branchName); + deleteDataset(projectId, "user_events"); + } + + public static void deleteAllEvents(String branchName) throws IOException { + System.out.println("Deleting events in process, please wait..."); + + // 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 (UserEventServiceClient eventServiceClient = UserEventServiceClient.create()) { + PurgeUserEventsRequest purgeUserEventsRequest = + PurgeUserEventsRequest.newBuilder().setParent(branchName).build(); + eventServiceClient.purgeUserEventsAsync(purgeUserEventsRequest); + System.out.printf("Events were deleted from %s%n", branchName); + } + } + + public static void deleteAllProducts(String branchName) throws IOException { + System.out.println("Deleting products in process, please wait..."); + + try (ProductServiceClient productServiceClient = ProductServiceClient.create()) { + ListProductsRequest listRequest = + ListProductsRequest.newBuilder().setParent(branchName).build(); + ListProductsPagedResponse products = productServiceClient.listProducts(listRequest); + + int deleteCount = 0; + + for (Product product : products.iterateAll()) { + DeleteProductRequest deleteRequest = + DeleteProductRequest.newBuilder().setName(product.getName()).build(); + + try { + productServiceClient.deleteProduct(deleteRequest); + deleteCount++; + } catch (PermissionDeniedException e) { + System.out.println( + "Ignore PermissionDenied in case the product does not exist " + + "at time of deletion."); + } + } + + System.out.printf("%s products were deleted from %s%n", deleteCount, branchName); + } + } +} diff --git a/retail/interactive-tutorials/src/main/java/events/setup/UpdateUserEventsJson.java b/retail/interactive-tutorials/src/main/java/events/setup/UpdateUserEventsJson.java new file mode 100644 index 00000000000..99c31edf15f --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/events/setup/UpdateUserEventsJson.java @@ -0,0 +1,53 @@ +/* + * 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 events.setup; + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +public class UpdateUserEventsJson { + + public static void main(String[] args) throws IOException { + String filePath = "src/main/resources/user_events.json"; + String invalidFilePath = "src/main/resources/user_events_some_invalid.json"; + updateEventsTimestamp(filePath); + updateEventsTimestamp(invalidFilePath); + } + + public static void updateEventsTimestamp(String jsonFile) throws IOException { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + Timestamp yesterdayDate = Timestamp.from(Instant.now().minus(1, ChronoUnit.DAYS)); + + String json = new String(Files.readAllBytes(Paths.get(jsonFile))); + json = + json.replaceAll( + "(\"eventTime\"\\s*:\\s*\"(\\d{4}-\\d{2}-\\d{2}(T.*Z)?))", + "\"eventTime\":\"" + dateFormat.format(yesterdayDate) + ""); + + BufferedWriter writer = new BufferedWriter(new FileWriter(jsonFile)); + writer.write(json); + System.out.printf("User events file '%s' updated.%n", jsonFile); + writer.close(); + } +} diff --git a/retail/interactive-tutorials/src/main/java/product/AddFulfillmentPlaces.java b/retail/interactive-tutorials/src/main/java/product/AddFulfillmentPlaces.java new file mode 100644 index 00000000000..0f8719650a7 --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/product/AddFulfillmentPlaces.java @@ -0,0 +1,83 @@ +/* + * 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 product; + +import static setup.SetupCleanup.createProduct; +import static setup.SetupCleanup.deleteProduct; +import static setup.SetupCleanup.getProduct; + +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.AddFulfillmentPlacesRequest; +import com.google.cloud.retail.v2.ProductServiceClient; +import java.io.IOException; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +public class AddFulfillmentPlaces { + + public static void main(String[] args) throws IOException, InterruptedException { + String projectId = ServiceOptions.getDefaultProjectId(); + String generatedProductId = UUID.randomUUID().toString(); + String productName = + String.format( + "projects/%s/locations/global/catalogs/default_catalog/branches/0/products/%s", + projectId, generatedProductId); + + createProduct(generatedProductId); + addFulfillmentPlaces(productName, "store2"); + getProduct(productName); + deleteProduct(productName); + } + + public static void addFulfillmentPlaces(String productName, String placeId) + throws IOException, InterruptedException { + + System.out.println("Add fulfilment places"); + + AddFulfillmentPlacesRequest addFulfillmentPlacesRequest = + AddFulfillmentPlacesRequest.newBuilder() + .setProduct(productName) + .setType("pickup-in-store") + .addPlaceIds(placeId) + .setAllowMissing(true) + .build(); + + // To send an out-of-order request assign the invalid AddTime here: + // Instant instant = LocalDateTime.now().minusDays(1).toInstant(ZoneOffset.UTC); + // Timestamp previousDay = Timestamp.newBuilder() + // .setSeconds(instant.getEpochSecond()) + // .setNanos(instant.getNano()) + // .build(); + // addFulfillmentPlacesRequest = + // addFulfillmentPlacesRequest.toBuilder().setAddTime(previousDay).build(); + + System.out.println("Add fulfillment request " + addFulfillmentPlacesRequest); + + // 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 (ProductServiceClient serviceClient = ProductServiceClient.create()) { + // This is a long-running operation and its result is not immediately + // present with get operations,thus we simulate wait with sleep method. + System.out.println("Waiting for operation to finish..."); + serviceClient.addFulfillmentPlacesAsync(addFulfillmentPlacesRequest).getPollingFuture().get(); + } catch (ExecutionException e) { + System.out.printf("Exception occurred during longrunning operation: %s%n", e.getMessage()); + } + } +} diff --git a/retail/interactive-tutorials/src/main/java/product/CreateProduct.java b/retail/interactive-tutorials/src/main/java/product/CreateProduct.java new file mode 100644 index 00000000000..9060a987965 --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/product/CreateProduct.java @@ -0,0 +1,88 @@ +/* + * 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. + */ + +/* + * Create product in a catalog using Retail API + */ + +package product; + +import static setup.SetupCleanup.deleteProduct; + +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.CreateProductRequest; +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 java.io.IOException; +import java.util.UUID; + +public class CreateProduct { + + public static void main(String[] args) throws IOException { + String projectId = ServiceOptions.getDefaultProjectId(); + String branchName = + String.format( + "projects/%s/locations/global/catalogs/default_catalog/branches/0", projectId); + String generatedProductId = UUID.randomUUID().toString(); + + Product createdProduct = createProduct(generatedProductId, branchName); + deleteProduct(createdProduct.getName()); + } + + // call the Retail API to create product + public static Product createProduct(String productId, String branchName) throws IOException { + float price = 30.0f; + float originalPrice = 35.5f; + + PriceInfo priceInfo = + PriceInfo.newBuilder() + .setPrice(price) + .setOriginalPrice(originalPrice) + .setCurrencyCode("USD") + .build(); + + Product generatedProduct = + Product.newBuilder() + .setTitle("Nest Mini") + .setType(Type.PRIMARY) + .addCategories("Speakers and displays") + .addBrands("Google") + .setPriceInfo(priceInfo) + .setAvailability(Availability.IN_STOCK) + .build(); + + CreateProductRequest createProductRequest = + CreateProductRequest.newBuilder() + .setProduct(generatedProduct) + .setProductId(productId) + .setParent(branchName) + .build(); + System.out.printf("Create product request: %s%n", createProductRequest); + + // 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 (ProductServiceClient serviceClient = ProductServiceClient.create()) { + Product createdProduct = serviceClient.createProduct(createProductRequest); + System.out.printf("Created product: %s%n", createdProduct); + return createdProduct; + } + } +} diff --git a/retail/interactive-tutorials/src/main/java/product/CrudProduct.java b/retail/interactive-tutorials/src/main/java/product/CrudProduct.java new file mode 100644 index 00000000000..16d98723219 --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/product/CrudProduct.java @@ -0,0 +1,176 @@ +/* + * 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. + */ + +/* + * Create, update, get and delete product in a catalog using Retail API. + */ + +package product; + +import com.google.api.gax.rpc.NotFoundException; +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.CreateProductRequest; +import com.google.cloud.retail.v2.DeleteProductRequest; +import com.google.cloud.retail.v2.GetProductRequest; +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.UpdateProductRequest; +import java.io.IOException; +import java.util.UUID; + +public class CrudProduct { + + public static void main(String[] args) throws IOException { + // TODO(developer): Set projectId to your Google Cloud Platform project ID. + String projectId = ServiceOptions.getDefaultProjectId(); + String generatedProductId = UUID.randomUUID().toString(); + String branchName = + String.format( + "projects/%s/locations/global/catalogs/default_catalog/branches/0", projectId); + String productName = String.format("%s/products/%s", branchName, generatedProductId); + + Product createdProduct = createProduct(generatedProductId, branchName); + getProduct(productName); + updateProduct(createdProduct, productName); + deleteProduct(productName); + } + + // Generate product for create + public static Product generateProduct() { + float price = 30.0f; + float originalPrice = 35.5f; + + PriceInfo priceInfo = + PriceInfo.newBuilder() + .setPrice(price) + .setOriginalPrice(originalPrice) + .setCurrencyCode("USD") + .build(); + + return Product.newBuilder() + .setTitle("Nest Mini") + .setType(Type.PRIMARY) + .addCategories("Speakers and displays") + .addBrands("Google") + .setPriceInfo(priceInfo) + .setAvailability(Availability.IN_STOCK) + .build(); + } + + // Generate product for update + public static Product generateProductForUpdate(String productId, String productName) { + final float price = 20.0f; + final float originalPrice = 25.5f; + + PriceInfo priceInfo = + PriceInfo.newBuilder() + .setPrice(price) + .setOriginalPrice(originalPrice) + .setCurrencyCode("EUR") + .build(); + + return Product.newBuilder() + .setId(productId) + .setName(productName) + .setTitle("Updated Nest Mini") + .setType(Type.PRIMARY) + .addCategories("Updated Speakers and displays") + .addBrands("Updated Google") + .setAvailability(Availability.OUT_OF_STOCK) + .setPriceInfo(priceInfo) + .build(); + } + + // Call the Retail API to create product + public static Product createProduct(String productId, String branchName) throws IOException { + CreateProductRequest createProductRequest = + CreateProductRequest.newBuilder() + .setProduct(generateProduct()) + .setProductId(productId) + .setParent(branchName) + .build(); + System.out.printf("Create product request: %s%n", createProductRequest); + + // 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 (ProductServiceClient serviceClient = ProductServiceClient.create()) { + Product createdProduct = serviceClient.createProduct(createProductRequest); + System.out.printf("Created product: %s%n", createdProduct); + return createdProduct; + } + } + + // Get product + public static Product getProduct(String productName) throws IOException { + Product product = Product.newBuilder().build(); + + GetProductRequest getProductRequest = + GetProductRequest.newBuilder().setName(productName).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 (ProductServiceClient serviceClient = ProductServiceClient.create()) { + product = serviceClient.getProduct(getProductRequest); + System.out.println("Get product response: " + product); + return product; + } catch (NotFoundException e) { + System.out.printf("Product %s not found", productName); + return product; + } + } + + // Update product + public static void updateProduct(Product originalProduct, String productName) throws IOException { + UpdateProductRequest updateProductRequest = + UpdateProductRequest.newBuilder() + .setProduct(generateProductForUpdate(originalProduct.getId(), productName)) + .setAllowMissing(true) + .build(); + System.out.printf("Update product request: %s%n", updateProductRequest); + + // 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 (ProductServiceClient serviceClient = ProductServiceClient.create()) { + Product updatedProduct = serviceClient.updateProduct(updateProductRequest); + System.out.printf("Updated product: %s%n", updatedProduct); + } + } + + // Delete product + public static void deleteProduct(String productName) throws IOException { + DeleteProductRequest deleteProductRequest = + DeleteProductRequest.newBuilder().setName(productName).build(); + System.out.printf("Delete product request %s%n", deleteProductRequest); + + // 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 (ProductServiceClient serviceClient = ProductServiceClient.create()) { + serviceClient.deleteProduct(deleteProductRequest); + System.out.printf("Product %s was deleted.%n", productName); + } + } +} diff --git a/retail/interactive-tutorials/src/main/java/product/DeleteProduct.java b/retail/interactive-tutorials/src/main/java/product/DeleteProduct.java new file mode 100644 index 00000000000..04055af1978 --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/product/DeleteProduct.java @@ -0,0 +1,54 @@ +/* + * 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. + */ + +/* + * Delete product from a catalog using Retail API + */ + +package product; + +import static setup.SetupCleanup.createProduct; + +import com.google.cloud.retail.v2.DeleteProductRequest; +import com.google.cloud.retail.v2.ProductServiceClient; +import java.io.IOException; +import java.util.UUID; + +public class DeleteProduct { + + public static void main(String[] args) throws IOException { + String generatedProductId = UUID.randomUUID().toString(); + + String createdProductName = createProduct(generatedProductId).getName(); + deleteProduct(createdProductName); + } + + // call the Retail API to delete product + public static void deleteProduct(String productName) throws IOException { + DeleteProductRequest deleteProductRequest = + DeleteProductRequest.newBuilder().setName(productName).build(); + System.out.printf("Delete product request %s%n", deleteProductRequest); + + // 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 (ProductServiceClient serviceClient = ProductServiceClient.create()) { + serviceClient.deleteProduct(deleteProductRequest); + System.out.printf("Product %s was deleted.%n", productName); + } + } +} diff --git a/retail/interactive-tutorials/src/main/java/product/GetProduct.java b/retail/interactive-tutorials/src/main/java/product/GetProduct.java new file mode 100644 index 00000000000..25a88c718d2 --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/product/GetProduct.java @@ -0,0 +1,63 @@ +/* + * 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. + */ + +/* + * Get product from a catalog using Retail API + */ + +package product; + +import static setup.SetupCleanup.createProduct; +import static setup.SetupCleanup.deleteProduct; + +import com.google.api.gax.rpc.NotFoundException; +import com.google.cloud.retail.v2.GetProductRequest; +import com.google.cloud.retail.v2.Product; +import com.google.cloud.retail.v2.ProductServiceClient; +import java.io.IOException; +import java.util.UUID; + +public class GetProduct { + + public static void main(String[] args) throws IOException { + String generatedProductId = UUID.randomUUID().toString(); + + Product createdProduct = createProduct(generatedProductId); + Product product = getProduct(createdProduct.getName()); + deleteProduct(product.getName()); + } + + // call the Retail API to get product + public static Product getProduct(String productName) throws IOException { + Product product = Product.newBuilder().build(); + + GetProductRequest getProductRequest = + GetProductRequest.newBuilder().setName(productName).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 (ProductServiceClient serviceClient = ProductServiceClient.create()) { + product = serviceClient.getProduct(getProductRequest); + System.out.println("Get product response: " + product); + return product; + } catch (NotFoundException e) { + System.out.printf("Product %s not found", productName); + return product; + } + } +} diff --git a/retail/interactive-tutorials/src/main/java/product/ImportProductsBigQueryTable.java b/retail/interactive-tutorials/src/main/java/product/ImportProductsBigQueryTable.java new file mode 100644 index 00000000000..83dce027efe --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/product/ImportProductsBigQueryTable.java @@ -0,0 +1,112 @@ +/* + * 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. + */ + +/* + * Import products into a catalog from big query table using Retail API + */ + +package product; + +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.BigQuerySource; +import com.google.cloud.retail.v2.ImportMetadata; +import com.google.cloud.retail.v2.ImportProductsRequest; +import com.google.cloud.retail.v2.ImportProductsRequest.ReconciliationMode; +import com.google.cloud.retail.v2.ImportProductsResponse; +import com.google.cloud.retail.v2.ProductInputConfig; +import com.google.cloud.retail.v2.ProductServiceClient; +import com.google.longrunning.Operation; +import com.google.longrunning.OperationsClient; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +public class ImportProductsBigQueryTable { + + public static void main(String[] args) throws IOException, InterruptedException { + String projectId = ServiceOptions.getDefaultProjectId(); + // To check for error handling, replace the below variable with the invalid branch name. + // String branchName = "invalid_branch_name"; + String branchName = + String.format( + "projects/%s/locations/global/catalogs/default_catalog/branches/0", projectId); + String datasetId = "products"; + // To check for error handling, replace the below variable with table id that contains invalid + // products. + // String tableId = "products_some_invalid"; + String tableId = "products"; + + importProductsFromBigQuery(projectId, branchName, datasetId, tableId); + } + + public static void importProductsFromBigQuery( + String projectId, String branchName, String datasetId, String tableId) + throws IOException, InterruptedException { + // TRY THE FULL RECONCILIATION MODE HERE: + ReconciliationMode reconciliationMode = ReconciliationMode.INCREMENTAL; + String dataSchema = "product"; + + BigQuerySource bigQuerySource = + BigQuerySource.newBuilder() + .setProjectId(projectId) + .setDatasetId(datasetId) + .setTableId(tableId) + .setDataSchema(dataSchema) + .build(); + + ProductInputConfig inputConfig = + ProductInputConfig.newBuilder().setBigQuerySource(bigQuerySource).build(); + + ImportProductsRequest importRequest = + ImportProductsRequest.newBuilder() + .setParent(branchName) + .setReconciliationMode(reconciliationMode) + .setInputConfig(inputConfig) + .build(); + System.out.printf("Import products from big query table request: %s%n", importRequest); + + // 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 (ProductServiceClient serviceClient = ProductServiceClient.create()) { + String operationName = serviceClient.importProductsCallable().call(importRequest).getName(); + System.out.printf("OperationName = %s%n", operationName); + + OperationsClient operationsClient = serviceClient.getOperationsClient(); + Operation operation = operationsClient.getOperation(operationName); + + while (!operation.getDone()) { + // Keep polling the operation periodically until the import task is done. + TimeUnit.SECONDS.sleep(30); + operation = operationsClient.getOperation(operationName); + } + + if (operation.hasMetadata()) { + ImportMetadata metadata = operation.getMetadata().unpack(ImportMetadata.class); + System.out.printf( + "Number of successfully imported products: %s%n", metadata.getSuccessCount()); + System.out.printf( + "Number of failures during the importing: %s%n", metadata.getFailureCount()); + } + + if (operation.hasResponse()) { + ImportProductsResponse response = + operation.getResponse().unpack(ImportProductsResponse.class); + System.out.printf("Operation result: %s%n", response); + } + } + } +} diff --git a/retail/interactive-tutorials/src/main/java/product/ImportProductsGcs.java b/retail/interactive-tutorials/src/main/java/product/ImportProductsGcs.java new file mode 100644 index 00000000000..2c0f8c807a8 --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/product/ImportProductsGcs.java @@ -0,0 +1,141 @@ +/* + * 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. + */ + +/* + * Import products into a catalog from gcs using Retail API + */ + +package product; + +import com.google.api.gax.rpc.InvalidArgumentException; +import com.google.api.gax.rpc.PermissionDeniedException; +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.GcsSource; +import com.google.cloud.retail.v2.ImportErrorsConfig; +import com.google.cloud.retail.v2.ImportMetadata; +import com.google.cloud.retail.v2.ImportProductsRequest; +import com.google.cloud.retail.v2.ImportProductsRequest.ReconciliationMode; +import com.google.cloud.retail.v2.ImportProductsResponse; +import com.google.cloud.retail.v2.ProductInputConfig; +import com.google.cloud.retail.v2.ProductServiceClient; +import com.google.longrunning.Operation; +import com.google.longrunning.OperationsClient; +import java.io.IOException; +import java.time.Instant; +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +public class ImportProductsGcs { + + public static void main(String[] args) throws IOException, InterruptedException { + String projectId = ServiceOptions.getDefaultProjectId(); + String branchName = + String.format( + "projects/%s/locations/global/catalogs/default_catalog/branches/0", projectId); + + String bucketName = System.getenv("BUCKET_NAME"); + String gcsBucket = String.format("gs://%s", bucketName); + String gcsErrorBucket = String.format("%s/errors", gcsBucket); + + // To check error handling, use an invalid catalog in request + // branchName = String.format( + // "projects/%s/locations/global/catalogs/invalid_catalog/branches/default_branch", + // projectId); + + String gcsProductsObject = "products.json"; + // To check error handling, use an invalid product JSON. + // gcsProductsObject = "products_some_invalid.json" + + importProductsFromGcs(branchName, gcsBucket, gcsProductsObject); + } + + public static void importProductsFromGcs( + String branchName, String gcsBucket, String gcsProductsObject) + throws IOException, InterruptedException { + String gcsErrorBucket = String.format("%s/errors", gcsBucket); + + GcsSource gcsSource = + GcsSource.newBuilder() + .addAllInputUris( + Collections.singleton(String.format("%s/%s", gcsBucket, gcsProductsObject))) + .build(); + + ProductInputConfig inputConfig = + ProductInputConfig.newBuilder().setGcsSource(gcsSource).build(); + + System.out.println("GRS source: " + gcsSource.getInputUrisList()); + + ImportErrorsConfig errorsConfig = + ImportErrorsConfig.newBuilder().setGcsPrefix(gcsErrorBucket).build(); + + ImportProductsRequest importRequest = + ImportProductsRequest.newBuilder() + .setParent(branchName) + .setReconciliationMode(ReconciliationMode.INCREMENTAL) + .setInputConfig(inputConfig) + .setErrorsConfig(errorsConfig) + .build(); + + System.out.printf("Import products from google cloud source request: %s%n", importRequest); + + // 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 (ProductServiceClient serviceClient = ProductServiceClient.create()) { + String operationName = serviceClient.importProductsCallable().call(importRequest).getName(); + + System.out.println("The operation was started."); + System.out.printf("OperationName = %s%n", operationName); + + OperationsClient operationsClient = serviceClient.getOperationsClient(); + Operation operation = operationsClient.getOperation(operationName); + + Instant deadline = Instant.now().plusSeconds(60); + + while (!operation.getDone() || Instant.now().isBefore(deadline)) { + System.out.println("Please wait till operation is done."); + TimeUnit.SECONDS.sleep(30); + operation = operationsClient.getOperation(operationName); + } + + if (operation.hasMetadata()) { + ImportMetadata metadata = operation.getMetadata().unpack(ImportMetadata.class); + System.out.printf( + "Number of successfully imported products: %s%n", metadata.getSuccessCount()); + System.out.printf( + "Number of failures during the importing: %s%n", metadata.getFailureCount()); + } else { + System.out.println("Metadata is empty."); + } + + if (operation.hasResponse()) { + ImportProductsResponse response = + operation.getResponse().unpack(ImportProductsResponse.class); + System.out.printf("Operation result: %s%n", response); + } else { + System.out.println("Operation result is empty."); + } + } catch (InvalidArgumentException e) { + System.out.printf( + "%s%n'%s' file does not exist in the bucket. Please " + + "make sure you have followed the setting up instructions.", + e.getMessage(), gcsProductsObject); + } catch (PermissionDeniedException e) { + System.out.println(e.getMessage()); + } + } +} diff --git a/retail/interactive-tutorials/src/main/java/product/ImportProductsInlineSource.java b/retail/interactive-tutorials/src/main/java/product/ImportProductsInlineSource.java new file mode 100644 index 00000000000..c16fc821ffe --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/product/ImportProductsInlineSource.java @@ -0,0 +1,210 @@ +/* + * 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. + */ + +/* + * Import products into a catalog from inline source using Retail API + */ + +package product; + +import com.google.api.gax.rpc.InvalidArgumentException; +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.ColorInfo; +import com.google.cloud.retail.v2.FulfillmentInfo; +import com.google.cloud.retail.v2.ImportMetadata; +import com.google.cloud.retail.v2.ImportProductsRequest; +import com.google.cloud.retail.v2.ImportProductsResponse; +import com.google.cloud.retail.v2.PriceInfo; +import com.google.cloud.retail.v2.Product; +import com.google.cloud.retail.v2.ProductInlineSource; +import com.google.cloud.retail.v2.ProductInputConfig; +import com.google.cloud.retail.v2.ProductServiceClient; +import com.google.longrunning.Operation; +import com.google.longrunning.OperationsClient; +import com.google.protobuf.FieldMask; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +public class ImportProductsInlineSource { + + public static void main(String[] args) throws IOException, InterruptedException { + String projectId = ServiceOptions.getDefaultProjectId(); + String branchName = + String.format( + "projects/%s/locations/global/catalogs/default_catalog/branches/0", projectId); + + importProductsInlineSource(branchName); + } + + public static void importProductsInlineSource(String branchName) + throws IOException, InterruptedException { + ProductInlineSource inlineSource = + ProductInlineSource.newBuilder().addAllProducts(getProducts()).build(); + + ProductInputConfig inputConfig = + ProductInputConfig.newBuilder().setProductInlineSource(inlineSource).build(); + + ImportProductsRequest importRequest = + ImportProductsRequest.newBuilder() + .setParent(branchName) + .setInputConfig(inputConfig) + .build(); + + System.out.printf("Import products from inline source request: %s%n", importRequest); + + // 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 (ProductServiceClient serviceClient = ProductServiceClient.create()) { + String operationName = serviceClient.importProductsCallable().call(importRequest).getName(); + System.out.printf("OperationName = %s%n", operationName); + + OperationsClient operationsClient = serviceClient.getOperationsClient(); + Operation operation = operationsClient.getOperation(operationName); + + long assuredBreak = System.currentTimeMillis() + 60000; // 60 seconds delay + + while (!operation.getDone() || System.currentTimeMillis() < assuredBreak) { + // Keep polling the operation periodically until the import task is done. + TimeUnit.SECONDS.sleep(30); + operation = operationsClient.getOperation(operationName); + } + + if (operation.hasMetadata()) { + ImportMetadata metadata = operation.getMetadata().unpack(ImportMetadata.class); + System.out.printf( + "Number of successfully imported products: %s%n", metadata.getSuccessCount()); + System.out.printf( + "Number of failures during the importing: %s%n", metadata.getFailureCount()); + } + + if (operation.hasResponse()) { + ImportProductsResponse response = + operation.getResponse().unpack(ImportProductsResponse.class); + System.out.printf("Operation result: %s%n", response); + } + } catch (InvalidArgumentException e) { + System.out.println(e.getMessage()); + } + } + + public static List getProducts() { + List products = new ArrayList<>(); + + Product product1; + Product product2; + + float price1 = 16f; + float originalPrice1 = 45.0f; + float cost1 = 12.0f; + + PriceInfo priceInfo1 = + PriceInfo.newBuilder() + .setPrice(price1) + .setOriginalPrice(originalPrice1) + .setCost(cost1) + .setCurrencyCode("USD") + .build(); + + ColorInfo colorInfo1 = + ColorInfo.newBuilder() + .addColorFamilies("Blue") + .addAllColors(Arrays.asList("Light blue", "Blue", "Dark blue")) + .build(); + + FulfillmentInfo fulfillmentInfo1 = + FulfillmentInfo.newBuilder() + .setType("pickup-in-store") + .addAllPlaceIds(Arrays.asList("store1", "store2")) + .build(); + + FieldMask fieldMask1 = + FieldMask.newBuilder() + .addAllPaths(Arrays.asList("title", "categories", "price_info", "color_info")) + .build(); + + // TO CHECK ERROR HANDLING COMMENT OUT THE PRODUCT TITLE HERE: + product1 = + Product.newBuilder() + .setTitle("#IamRemarkable Pen") + .setId(UUID.randomUUID().toString()) + .addAllCategories(Collections.singletonList("Office")) + .setUri( + "https://shop.googlemerchandisestore.com/Google+Redesign/" + + "Office/IamRemarkable+Pen") + .addBrands("#IamRemarkable") + .setPriceInfo(priceInfo1) + .setColorInfo(colorInfo1) + .addFulfillmentInfo(fulfillmentInfo1) + .setRetrievableFields(fieldMask1) + .build(); + + float price2 = 35f; + float originalPrice2 = 45.0f; + float cost2 = 12.0f; + + PriceInfo priceInfo2 = + PriceInfo.newBuilder() + .setPrice(price2) + .setOriginalPrice(originalPrice2) + .setCost(cost2) + .setCurrencyCode("USD") + .build(); + + ColorInfo colorInfo2 = + ColorInfo.newBuilder() + .addColorFamilies("Blue") + .addAllColors(Collections.singletonList("Sky blue")) + .build(); + + FulfillmentInfo fulfillmentInfo2 = + FulfillmentInfo.newBuilder() + .setType("pickup-in-store") + .addAllPlaceIds(Arrays.asList("store2", "store3")) + .build(); + + FieldMask fieldMask2 = + FieldMask.newBuilder() + .addAllPaths(Arrays.asList("title", "categories", "price_info", "color_info")) + .build(); + + product2 = + Product.newBuilder() + .setTitle("Android Embroidered Crewneck Sweater") + .setId(UUID.randomUUID().toString()) + .addCategories("Apparel") + .setUri( + "https://shop.googlemerchandisestore.com/Google+Redesign/" + + "Apparel/Android+Embroidered+Crewneck+Sweater") + .addBrands("Android") + .setPriceInfo(priceInfo2) + .setColorInfo(colorInfo2) + .addFulfillmentInfo(fulfillmentInfo2) + .setRetrievableFields(fieldMask2) + .build(); + + products.add(product1); + products.add(product2); + + return products; + } +} diff --git a/retail/interactive-tutorials/src/main/java/product/RemoveFulfillmentPlaces.java b/retail/interactive-tutorials/src/main/java/product/RemoveFulfillmentPlaces.java new file mode 100644 index 00000000000..6a532dcacf7 --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/product/RemoveFulfillmentPlaces.java @@ -0,0 +1,84 @@ +/* + * 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 product; + +import static setup.SetupCleanup.createProduct; +import static setup.SetupCleanup.deleteProduct; +import static setup.SetupCleanup.getProduct; + +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.ProductServiceClient; +import com.google.cloud.retail.v2.RemoveFulfillmentPlacesRequest; +import java.io.IOException; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +public class RemoveFulfillmentPlaces { + + public static void main(String[] args) throws IOException, InterruptedException { + String projectId = ServiceOptions.getDefaultProjectId(); + String generatedProductId = UUID.randomUUID().toString(); + String productName = + String.format( + "projects/%s/locations/global/catalogs/default_catalog/branches/0/products/%s", + projectId, generatedProductId); + + createProduct(generatedProductId); + removeFulfillmentPlaces(productName, "store0"); + getProduct(productName); + deleteProduct(productName); + } + + // remove fulfillment places to product + public static void removeFulfillmentPlaces(String productName, String storeId) + throws IOException, InterruptedException { + + System.out.println("Remove fulfilment places with current date"); + + RemoveFulfillmentPlacesRequest removeFulfillmentRequest = + RemoveFulfillmentPlacesRequest.newBuilder() + .setProduct(productName) + .setType("pickup-in-store") + .addPlaceIds(storeId) + .setAllowMissing(true) + .build(); + + // To send an out-of-order request assign the invalid RemoveTime here: + // Instant instant = LocalDateTime.now().minusDays(1).toInstant(ZoneOffset.UTC); + // Timestamp previousDay = Timestamp.newBuilder() + // .setSeconds(instant.getEpochSecond()) + // .setNanos(instant.getNano()) + // .build(); + // removeFulfillmentRequest = + // removeFulfillmentRequest.toBuilder().setRemoveTime(previousDay).build(); + + System.out.println("Remove fulfillment request " + removeFulfillmentRequest); + + // 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 (ProductServiceClient serviceClient = ProductServiceClient.create()) { + // This is a long-running operation and its result is not immediately + // present with get operations,thus we simulate wait with sleep method. + System.out.println("Waiting for operation to finish..."); + serviceClient.removeFulfillmentPlacesAsync(removeFulfillmentRequest).getPollingFuture().get(); + } catch (ExecutionException e) { + System.out.printf("Exception occurred during longrunning operation: %s%n", e.getMessage()); + } + } +} diff --git a/retail/interactive-tutorials/src/main/java/product/SetInventory.java b/retail/interactive-tutorials/src/main/java/product/SetInventory.java new file mode 100644 index 00000000000..5a5f1441037 --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/product/SetInventory.java @@ -0,0 +1,117 @@ +/* + * 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 product; + +import static setup.SetupCleanup.createProduct; +import static setup.SetupCleanup.deleteProduct; +import static setup.SetupCleanup.getProduct; + +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.FulfillmentInfo; +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.ProductServiceClient; +import com.google.cloud.retail.v2.SetInventoryRequest; +import com.google.protobuf.FieldMask; +import com.google.protobuf.Int32Value; +import java.io.IOException; +import java.util.Arrays; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +public class SetInventory { + + public static void main(String[] args) throws IOException, InterruptedException { + String projectId = ServiceOptions.getDefaultProjectId(); + String generatedProductId = UUID.randomUUID().toString(); + String productName = + String.format( + "projects/%s/locations/global/catalogs/default_catalog/branches/0/products/%s", + projectId, generatedProductId); + + createProduct(generatedProductId); + setInventory(productName); + getProduct(productName); + deleteProduct(productName); + } + + public static void setInventory(String productName) throws IOException, InterruptedException { + float price = 15.0f; + float originalPrice = 20.0f; + float cost = 8.0f; + + FieldMask setMask = + FieldMask.newBuilder() + .addAllPaths( + Arrays.asList( + "price_info", "availability", "fulfillment_info", "available_quantity")) + .build(); + + PriceInfo priceInfo = + PriceInfo.newBuilder() + .setPrice(price) + .setOriginalPrice(originalPrice) + .setCost(cost) + .setCurrencyCode("USD") + .build(); + + FulfillmentInfo fulfillmentInfo = + FulfillmentInfo.newBuilder() + .setType("pickup-in-store") + .addAllPlaceIds(Arrays.asList("store1", "store2")) + .build(); + + Product product = + Product.newBuilder() + .setName(productName) + .setPriceInfo(priceInfo) + .addFulfillmentInfo(fulfillmentInfo) + .setAvailability(Availability.IN_STOCK) + .setAvailableQuantity(Int32Value.newBuilder().setValue(5).build()) + .build(); + + SetInventoryRequest setInventoryRequest = + SetInventoryRequest.newBuilder() + .setInventory(product) + .setAllowMissing(true) + .setSetMask(setMask) + .build(); + System.out.printf("Set inventory request: %s%n", setInventoryRequest); + + // 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() + // .setSeconds(instant.getEpochSecond()) + // .setNanos(instant.getNano()) + // .build(); + // setInventoryRequest = setInventoryRequest.toBuilder().setSetTime(previousDay).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 (ProductServiceClient serviceClient = ProductServiceClient.create()) { + // This is a long-running operation and its result is not immediately + // present with get operations,thus we simulate wait with sleep method. + System.out.println("Waiting for operation to finish..."); + serviceClient.setInventoryAsync(setInventoryRequest).getPollingFuture().get(); + } catch (ExecutionException e) { + System.out.printf("Exception occurred during longrunning operation: %s%n", e.getMessage()); + } + } +} diff --git a/retail/interactive-tutorials/src/main/java/product/UpdateProduct.java b/retail/interactive-tutorials/src/main/java/product/UpdateProduct.java new file mode 100644 index 00000000000..4d37ad2220a --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/product/UpdateProduct.java @@ -0,0 +1,93 @@ +/* + * 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. + */ + +/* + * Update product in a catalog using Retail API + */ + +package product; + +import static setup.SetupCleanup.createProduct; +import static setup.SetupCleanup.deleteProduct; + +import com.google.cloud.ServiceOptions; +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.UpdateProductRequest; +import java.io.IOException; +import java.util.UUID; + +public class UpdateProduct { + + public static void main(String[] args) throws IOException { + String projectId = ServiceOptions.getDefaultProjectId(); + String branchName = + String.format( + "projects/%s/locations/global/catalogs/default_catalog/branches/0", projectId); + String generatedProductId = UUID.randomUUID().toString(); + + Product createdProduct = createProduct(generatedProductId); + updateProduct(createdProduct, branchName); + deleteProduct(createdProduct.getName()); + } + + // call the Retail API to update product + public static void updateProduct(Product originalProduct, String defaultBranchName) + throws IOException { + final float price = 20.0f; + final float originalPrice = 25.5f; + + PriceInfo priceInfo = + PriceInfo.newBuilder() + .setPrice(price) + .setOriginalPrice(originalPrice) + .setCurrencyCode("EUR") + .build(); + + Product generatedProduct = + Product.newBuilder() + .setId(originalProduct.getId()) + .setName(defaultBranchName + "/products/" + originalProduct.getId()) + .setTitle("Updated Nest Mini") + .setType(Type.PRIMARY) + .addCategories("Updated Speakers and displays") + .addBrands("Updated Google") + .setAvailability(Availability.OUT_OF_STOCK) + .setPriceInfo(priceInfo) + .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 (ProductServiceClient serviceClient = ProductServiceClient.create()) { + UpdateProductRequest updateProductRequest = + UpdateProductRequest.newBuilder() + .setProduct(generatedProduct) + .setAllowMissing(true) + .build(); + System.out.printf("Update product request: %s%n", updateProductRequest); + + // PASTE UPDATE MASK HERE: requires import com.google.protobuf.FieldMask + + Product updatedProduct = serviceClient.updateProduct(updateProductRequest); + System.out.printf("Updated product: %s%n", updatedProduct); + } + } +} diff --git a/retail/interactive-tutorials/src/main/java/product/setup/ProductsCreateBigqueryTable.java b/retail/interactive-tutorials/src/main/java/product/setup/ProductsCreateBigqueryTable.java new file mode 100644 index 00000000000..3638b6c79ac --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/product/setup/ProductsCreateBigqueryTable.java @@ -0,0 +1,55 @@ +/* + * 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 product.setup; + +import static setup.SetupCleanup.createBqDataset; +import static setup.SetupCleanup.createBqTable; +import static setup.SetupCleanup.getGson; +import static setup.SetupCleanup.uploadDataToBqTable; + +import com.google.cloud.bigquery.Field; +import com.google.cloud.bigquery.Schema; +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.stream.Collectors; + +public class ProductsCreateBigqueryTable { + + public static void main(String[] args) throws IOException { + final String dataset = "products"; + final String validProductsTable = "products"; + final String invalidProductsTable = "products_some_invalid"; + final String productSchemaFilePath = "src/main/resources/product_schema.json"; + final String validProductsSourceFile = + ProductsCreateBigqueryTable.class.getResource("/products.json").getPath(); + final String invalidProductsSourceFile = + ProductsCreateBigqueryTable.class.getResource("/products_some_invalid.json").getPath(); + + BufferedReader bufferedReader = new BufferedReader(new FileReader(productSchemaFilePath)); + String jsonToString = bufferedReader.lines().collect(Collectors.joining()); + jsonToString = jsonToString.replace("\"fields\"", "\"subFields\""); + Field[] fields = getGson().fromJson(jsonToString, Field[].class); + Schema productSchema = Schema.of(fields); + + createBqDataset(dataset); + createBqTable(dataset, validProductsTable, productSchema); + uploadDataToBqTable(dataset, validProductsTable, validProductsSourceFile); + createBqTable(dataset, invalidProductsTable, productSchema); + uploadDataToBqTable(dataset, invalidProductsTable, invalidProductsSourceFile); + } +} diff --git a/retail/interactive-tutorials/src/main/java/product/setup/ProductsCreateGcsBucket.java b/retail/interactive-tutorials/src/main/java/product/setup/ProductsCreateGcsBucket.java new file mode 100644 index 00000000000..80ed6285dcd --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/product/setup/ProductsCreateGcsBucket.java @@ -0,0 +1,60 @@ +/* + * 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 product.setup; + +import static setup.SetupCleanup.createBucket; +import static setup.SetupCleanup.uploadObject; + +import com.google.cloud.ServiceOptions; +import com.google.protobuf.Timestamp; +import java.io.IOException; +import java.time.Instant; + +public class ProductsCreateGcsBucket { + + private static final String PROJECT_ID = ServiceOptions.getDefaultProjectId(); + + private static final Timestamp CURRENT_DATE = + Timestamp.newBuilder() + .setSeconds(Instant.now().getEpochSecond()) + .setNanos(Instant.now().getNano()) + .build(); + + private static final String BUCKET_NAME = + String.format("%s_products_%s", PROJECT_ID, CURRENT_DATE.getSeconds()); + + public static void main(String... args) throws IOException { + createGcsBucketAndUploadData(BUCKET_NAME); + } + + public static void createGcsBucketAndUploadData(String bucketName) throws IOException { + createBucket(bucketName); + System.out.printf("Products gcs bucket %s was created.%n", bucketName); + + uploadObject(bucketName, "products.json", "src/main/resources/products.json"); + System.out.printf("File 'products.json' was uploaded into bucket '%s'.%n", bucketName); + + uploadObject( + bucketName, "products_some_invalid.json", "src/main/resources/products_some_invalid.json"); + System.out.printf( + "File 'products_some_invalid.json' was uploaded into bucket '%s'.%n", bucketName); + } + + public static String getBucketName() { + return BUCKET_NAME; + } +} diff --git a/retail/interactive-tutorials/src/main/java/product/setup/RemoveProductsResources.java b/retail/interactive-tutorials/src/main/java/product/setup/RemoveProductsResources.java new file mode 100644 index 00000000000..de7aea24335 --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/product/setup/RemoveProductsResources.java @@ -0,0 +1,76 @@ +/* + * 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 product.setup; + +import static setup.SetupCleanup.deleteBucket; +import static setup.SetupCleanup.deleteDataset; + +import com.google.api.gax.rpc.PermissionDeniedException; +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.DeleteProductRequest; +import com.google.cloud.retail.v2.ListProductsRequest; +import com.google.cloud.retail.v2.Product; +import com.google.cloud.retail.v2.ProductServiceClient; +import com.google.cloud.retail.v2.ProductServiceClient.ListProductsPagedResponse; +import java.io.IOException; + +public class RemoveProductsResources { + + public static void main(String[] args) throws IOException { + String projectId = ServiceOptions.getDefaultProjectId(); + String bucketName = System.getenv("BUCKET_NAME"); + String branchName = + String.format( + "projects/%s/locations/global/catalogs/default_catalog/branches/0", projectId); + + deleteBucket(bucketName); + deleteAllProducts(branchName); + deleteDataset(projectId, "products"); + } + + public static void deleteAllProducts(String branchName) throws IOException { + System.out.println("Deleting products in process, please wait..."); + + // 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 (ProductServiceClient productServiceClient = ProductServiceClient.create()) { + ListProductsRequest listRequest = + ListProductsRequest.newBuilder().setParent(branchName).build(); + ListProductsPagedResponse products = productServiceClient.listProducts(listRequest); + + int deleteCount = 0; + + for (Product product : products.iterateAll()) { + DeleteProductRequest deleteRequest = + DeleteProductRequest.newBuilder().setName(product.getName()).build(); + + try { + productServiceClient.deleteProduct(deleteRequest); + deleteCount++; + } catch (PermissionDeniedException e) { + System.out.println( + "Ignore PermissionDenied in case the product does not exist " + + "at time of deletion."); + } + } + + System.out.printf("%s products were deleted from %s%n", deleteCount, branchName); + } + } +} diff --git a/retail/interactive-tutorials/src/main/java/search/SearchSimpleQuery.java b/retail/interactive-tutorials/src/main/java/search/SearchSimpleQuery.java new file mode 100644 index 00000000000..e2bc4c3639c --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/search/SearchSimpleQuery.java @@ -0,0 +1,70 @@ +/* + * 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. + */ + +/* + * Call Retail API to search for a products in a catalog + * using only search query. + */ + +package search; + +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.SearchRequest; +import com.google.cloud.retail.v2.SearchResponse; +import com.google.cloud.retail.v2.SearchServiceClient; +import java.io.IOException; +import java.util.UUID; + +public class SearchSimpleQuery { + + public static void main(String[] args) throws IOException { + String projectId = ServiceOptions.getDefaultProjectId(); + String defaultCatalogName = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + String defaultSearchPlacementName = defaultCatalogName + "/placements/default_search"; + + searchResponse(defaultSearchPlacementName); + } + + public static void searchResponse(String defaultSearchPlacementName) throws IOException { + // TRY DIFFERENT QUERY PHRASES HERE: + String queryPhrase = "Hoodie"; + String visitorId = UUID.randomUUID().toString(); + int pageSize = 10; + + SearchRequest searchRequest = + SearchRequest.newBuilder() + .setPlacement(defaultSearchPlacementName) + .setQuery(queryPhrase) + .setVisitorId(visitorId) + .setPageSize(pageSize) + .build(); + System.out.println("Search request: " + searchRequest); + + // 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 (SearchServiceClient client = SearchServiceClient.create()) { + SearchResponse searchResponse = client.search(searchRequest).getPage().getResponse(); + if (searchResponse.getTotalSize() == 0) { + System.out.println("The search operation returned no matching results."); + } else { + System.out.println("Search response: " + searchResponse); + } + } + } +} diff --git a/retail/interactive-tutorials/src/main/java/search/SearchWithBoostSpec.java b/retail/interactive-tutorials/src/main/java/search/SearchWithBoostSpec.java new file mode 100644 index 00000000000..caf5df7dba0 --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/search/SearchWithBoostSpec.java @@ -0,0 +1,81 @@ +/* + * 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. + */ + +/* + * Call Retail API to search for a products in a catalog, rerank the + * results boosting or burying the products that match defined condition. + */ + +package search; + +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.SearchRequest; +import com.google.cloud.retail.v2.SearchRequest.BoostSpec; +import com.google.cloud.retail.v2.SearchRequest.BoostSpec.ConditionBoostSpec; +import com.google.cloud.retail.v2.SearchResponse; +import com.google.cloud.retail.v2.SearchServiceClient; +import java.io.IOException; +import java.util.UUID; + +public class SearchWithBoostSpec { + + public static void main(String[] args) throws IOException { + String projectId = ServiceOptions.getDefaultProjectId(); + String defaultCatalogName = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + String defaultSearchPlacementName = defaultCatalogName + "/placements/default_search"; + + searchResponse(defaultSearchPlacementName); + } + + public static void searchResponse(String defaultSearchPlacementName) throws IOException { + // TRY DIFFERENT CONDITIONS HERE: + String searchQuery = "Tee"; + String condition = "(colorFamilies: ANY(\"Blue\"))"; + float boost = 0.0f; + int pageSize = 10; + String visitorId = UUID.randomUUID().toString(); + + BoostSpec boostSpec = + BoostSpec.newBuilder() + .addConditionBoostSpecs( + ConditionBoostSpec.newBuilder().setCondition(condition).setBoost(boost).build()) + .build(); + + SearchRequest searchRequest = + SearchRequest.newBuilder() + .setPlacement(defaultSearchPlacementName) + .setQuery(searchQuery) + .setVisitorId(visitorId) + .setBoostSpec(boostSpec) + .setPageSize(pageSize) + .build(); + System.out.println("Search request: " + searchRequest); + + // 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 (SearchServiceClient client = SearchServiceClient.create()) { + SearchResponse searchResponse = client.search(searchRequest).getPage().getResponse(); + if (searchResponse.getTotalSize() == 0) { + System.out.println("The search operation returned no matching results."); + } else { + System.out.println("Search response: " + searchResponse); + } + } + } +} diff --git a/retail/interactive-tutorials/src/main/java/search/SearchWithFacetSpec.java b/retail/interactive-tutorials/src/main/java/search/SearchWithFacetSpec.java new file mode 100644 index 00000000000..9688a871c78 --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/search/SearchWithFacetSpec.java @@ -0,0 +1,72 @@ +/* + * 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 search; + +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.SearchRequest; +import com.google.cloud.retail.v2.SearchRequest.FacetSpec; +import com.google.cloud.retail.v2.SearchRequest.FacetSpec.FacetKey; +import com.google.cloud.retail.v2.SearchResponse; +import com.google.cloud.retail.v2.SearchServiceClient; +import java.io.IOException; +import java.util.UUID; + +public class SearchWithFacetSpec { + + public static void main(String[] args) throws IOException { + String projectId = ServiceOptions.getDefaultProjectId(); + String defaultCatalogName = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + String defaultSearchPlacementName = defaultCatalogName + "/placements/default_search"; + + searchResponse(defaultSearchPlacementName); + } + + public static void searchResponse(String defaultSearchPlacementName) throws IOException { + // TRY DIFFERENT CONDITIONS HERE: + String searchQuery = "Tee"; + String facetKeyParam = "colorFamilies"; + int pageSize = 10; + String visitorId = UUID.randomUUID().toString(); + + FacetKey facetKey = FacetKey.newBuilder().setKey(facetKeyParam).build(); + FacetSpec facetSpec = FacetSpec.newBuilder().setFacetKey(facetKey).build(); + + SearchRequest searchRequest = + SearchRequest.newBuilder() + .setPlacement(defaultSearchPlacementName) + .setQuery(searchQuery) + .setVisitorId(visitorId) + .addFacetSpecs(facetSpec) + .setPageSize(pageSize) + .build(); + System.out.println("Search request: " + searchRequest); + + // 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 (SearchServiceClient client = SearchServiceClient.create()) { + SearchResponse searchResponse = client.search(searchRequest).getPage().getResponse(); + if (searchResponse.getTotalSize() == 0) { + System.out.println("The search operation returned no matching results."); + } else { + System.out.println("Search response: " + searchResponse); + } + } + } +} diff --git a/retail/interactive-tutorials/src/main/java/search/SearchWithFiltering.java b/retail/interactive-tutorials/src/main/java/search/SearchWithFiltering.java new file mode 100644 index 00000000000..1c46b16454d --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/search/SearchWithFiltering.java @@ -0,0 +1,73 @@ +/* + * 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. + */ + +/* + * Call Retail API to search for a products in a catalog, + * filter the results by different product fields. + */ + +package search; + +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.SearchRequest; +import com.google.cloud.retail.v2.SearchResponse; +import com.google.cloud.retail.v2.SearchServiceClient; +import java.io.IOException; +import java.util.UUID; + +public class SearchWithFiltering { + + public static void main(String[] args) throws IOException { + String projectId = ServiceOptions.getDefaultProjectId(); + String defaultCatalogName = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + String defaultSearchPlacementName = defaultCatalogName + "/placements/default_search"; + + searchResponse(defaultSearchPlacementName); + } + + public static void searchResponse(String defaultSearchPlacementName) throws IOException { + // TRY DIFFERENT FILTER EXPRESSIONS HERE: + String filter = "(colorFamilies: ANY(\"Black\"))"; + String queryPhrase = "Tee"; + int pageSize = 10; + String visitorId = UUID.randomUUID().toString(); + + SearchRequest searchRequest = + SearchRequest.newBuilder() + .setPlacement(defaultSearchPlacementName) + .setVisitorId(visitorId) + .setQuery(queryPhrase) + .setPageSize(pageSize) + .setFilter(filter) + .build(); + + System.out.println("Search request: " + searchRequest); + + // 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 (SearchServiceClient client = SearchServiceClient.create()) { + SearchResponse searchResponse = client.search(searchRequest).getPage().getResponse(); + if (searchResponse.getTotalSize() == 0) { + System.out.println("The search operation returned no matching results."); + } else { + System.out.println("Search response: " + searchResponse); + } + } + } +} diff --git a/retail/interactive-tutorials/src/main/java/search/SearchWithOrdering.java b/retail/interactive-tutorials/src/main/java/search/SearchWithOrdering.java new file mode 100644 index 00000000000..d3139a080e9 --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/search/SearchWithOrdering.java @@ -0,0 +1,72 @@ +/* + * 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. + */ + +/* + * Call Retail API to search for a products in a catalog, + * order the results by different product fields. + */ + +package search; + +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.SearchRequest; +import com.google.cloud.retail.v2.SearchResponse; +import com.google.cloud.retail.v2.SearchServiceClient; +import java.io.IOException; +import java.util.UUID; + +public class SearchWithOrdering { + + public static void main(String[] args) throws IOException { + String projectId = ServiceOptions.getDefaultProjectId(); + String defaultCatalogName = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + String defaultSearchPlacementName = defaultCatalogName + "/placements/default_search"; + + searchResponse(defaultSearchPlacementName); + } + + public static void searchResponse(String defaultSearchPlacementName) throws IOException { + // TRY DIFFERENT ORDER BY EXPRESSION HERE: + String order = "price desc"; + String queryPhrase = "Hoodie"; + int pageSize = 10; + String visitorId = UUID.randomUUID().toString(); + + SearchRequest searchRequest = + SearchRequest.newBuilder() + .setPlacement(defaultSearchPlacementName) + .setQuery(queryPhrase) + .setOrderBy(order) + .setVisitorId(visitorId) + .setPageSize(pageSize) + .build(); + System.out.println("Search request: " + searchRequest); + + // 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 (SearchServiceClient client = SearchServiceClient.create()) { + SearchResponse searchResponse = client.search(searchRequest).getPage().getResponse(); + if (searchResponse.getTotalSize() == 0) { + System.out.println("The search operation returned no matching results."); + } else { + System.out.println("Search response: " + searchResponse); + } + } + } +} diff --git a/retail/interactive-tutorials/src/main/java/search/SearchWithPagination.java b/retail/interactive-tutorials/src/main/java/search/SearchWithPagination.java new file mode 100644 index 00000000000..c3d80c1ac62 --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/search/SearchWithPagination.java @@ -0,0 +1,79 @@ +/* + * 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. + */ + +/* + * Call Retail API to search for a products in a catalog, + * limit the number of the products per page and go to the next page + * using "next_page_token" or jump to chosen page using "offset". + */ + +package search; + +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.SearchRequest; +import com.google.cloud.retail.v2.SearchResponse; +import com.google.cloud.retail.v2.SearchServiceClient; +import java.io.IOException; +import java.util.UUID; + +public class SearchWithPagination { + + public static void main(String[] args) throws IOException { + String projectId = ServiceOptions.getDefaultProjectId(); + String defaultCatalogName = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + String defaultSearchPlacementName = defaultCatalogName + "/placements/default_search"; + + searchResponse(defaultSearchPlacementName); + } + + public static void searchResponse(String defaultSearchPlacementName) throws IOException { + // TRY DIFFERENT PAGINATION PARAMETERS HERE: + int pageSize = 6; + String queryPhrase = "Hoodie"; + int offset = 0; + String pageToken = ""; + String visitorId = UUID.randomUUID().toString(); + + SearchRequest searchRequest = + SearchRequest.newBuilder() + .setPlacement(defaultSearchPlacementName) + .setVisitorId(visitorId) + .setQuery(queryPhrase) + .setPageSize(pageSize) + .setOffset(offset) + .setPageToken(pageToken) + .build(); + System.out.println("Search request: " + searchRequest); + + // 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 (SearchServiceClient client = SearchServiceClient.create()) { + SearchResponse searchResponseFirstPage = client.search(searchRequest).getPage().getResponse(); + if (searchResponseFirstPage.getTotalSize() == 0) { + System.out.println("The search operation returned no matching results."); + } else { + System.out.println("Search response: " + searchResponseFirstPage); + } + + // PASTE CALL WITH NEXT PAGE TOKEN HERE: + + // PASTE CALL WITH OFFSET HERE: + } + } +} diff --git a/retail/interactive-tutorials/src/main/java/search/SearchWithQueryExpansionSpec.java b/retail/interactive-tutorials/src/main/java/search/SearchWithQueryExpansionSpec.java new file mode 100644 index 00000000000..0576de17e2b --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/search/SearchWithQueryExpansionSpec.java @@ -0,0 +1,78 @@ +/* + * 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. + */ + +/* + * Call Retail API to search for a products in a catalog, + * enabling the query expansion feature to let the Google Retail Search + * build an automatic query expansion. + */ + +package search; + +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.SearchRequest; +import com.google.cloud.retail.v2.SearchRequest.QueryExpansionSpec; +import com.google.cloud.retail.v2.SearchRequest.QueryExpansionSpec.Condition; +import com.google.cloud.retail.v2.SearchResponse; +import com.google.cloud.retail.v2.SearchServiceClient; +import java.io.IOException; +import java.util.UUID; + +public class SearchWithQueryExpansionSpec { + + public static void main(String[] args) throws IOException { + String projectId = ServiceOptions.getDefaultProjectId(); + String defaultCatalogName = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + String defaultSearchPlacementName = defaultCatalogName + "/placements/default_search"; + + searchResponse(defaultSearchPlacementName); + } + + public static void searchResponse(String defaultSearchPlacementName) throws IOException { + // TRY DIFFERENT QUERY EXPANSION CONDITION HERE: + Condition condition = Condition.AUTO; + int pageSize = 10; + String queryPhrase = "Google Youth Hero Tee Grey"; + String visitorId = UUID.randomUUID().toString(); + + QueryExpansionSpec queryExpansionSpec = + QueryExpansionSpec.newBuilder().setCondition(condition).build(); + + SearchRequest searchRequest = + SearchRequest.newBuilder() + .setPlacement(defaultSearchPlacementName) + .setQuery(queryPhrase) + .setVisitorId(visitorId) + .setQueryExpansionSpec(queryExpansionSpec) + .setPageSize(pageSize) + .build(); + System.out.println("Search request: " + searchRequest); + + // 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 (SearchServiceClient client = SearchServiceClient.create()) { + SearchResponse searchResponse = client.search(searchRequest).getPage().getResponse(); + if (searchResponse.getTotalSize() == 0) { + System.out.println("The search operation returned no matching results."); + } else { + System.out.println("Search response: " + searchResponse); + } + } + } +} diff --git a/retail/interactive-tutorials/src/main/java/setup/SetupCleanup.java b/retail/interactive-tutorials/src/main/java/setup/SetupCleanup.java new file mode 100644 index 00000000000..9e6b3d5c64e --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/setup/SetupCleanup.java @@ -0,0 +1,407 @@ +/* + * 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 setup; + +import static com.google.cloud.storage.StorageClass.STANDARD; + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.api.gax.paging.Page; +import com.google.api.gax.rpc.NotFoundException; +import com.google.cloud.ServiceOptions; +import com.google.cloud.bigquery.BigQuery; +import com.google.cloud.bigquery.BigQuery.DatasetDeleteOption; +import com.google.cloud.bigquery.BigQueryException; +import com.google.cloud.bigquery.BigQueryOptions; +import com.google.cloud.bigquery.Dataset; +import com.google.cloud.bigquery.DatasetId; +import com.google.cloud.bigquery.DatasetInfo; +import com.google.cloud.bigquery.Field; +import com.google.cloud.bigquery.FieldList; +import com.google.cloud.bigquery.FormatOptions; +import com.google.cloud.bigquery.Job; +import com.google.cloud.bigquery.JobId; +import com.google.cloud.bigquery.LegacySQLTypeName; +import com.google.cloud.bigquery.Schema; +import com.google.cloud.bigquery.StandardTableDefinition; +import com.google.cloud.bigquery.TableDataWriteChannel; +import com.google.cloud.bigquery.TableDefinition; +import com.google.cloud.bigquery.TableId; +import com.google.cloud.bigquery.TableInfo; +import com.google.cloud.bigquery.WriteChannelConfiguration; +import com.google.cloud.retail.v2.CreateProductRequest; +import com.google.cloud.retail.v2.DeleteProductRequest; +import com.google.cloud.retail.v2.FulfillmentInfo; +import com.google.cloud.retail.v2.GetProductRequest; +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.ProductDetail; +import com.google.cloud.retail.v2.ProductServiceClient; +import com.google.cloud.retail.v2.PurgeMetadata; +import com.google.cloud.retail.v2.PurgeUserEventsRequest; +import com.google.cloud.retail.v2.PurgeUserEventsResponse; +import com.google.cloud.retail.v2.UserEvent; +import com.google.cloud.retail.v2.UserEventServiceClient; +import com.google.cloud.retail.v2.WriteUserEventRequest; +import com.google.cloud.storage.Blob; +import com.google.cloud.storage.BlobId; +import com.google.cloud.storage.BlobInfo; +import com.google.cloud.storage.Bucket; +import com.google.cloud.storage.BucketInfo; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageException; +import com.google.cloud.storage.StorageOptions; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializer; +import com.google.protobuf.Int32Value; +import com.google.protobuf.Timestamp; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.channels.Channels; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.Instant; +import java.util.Arrays; +import java.util.Collections; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +public class SetupCleanup { + + private static final String PROJECT_ID = ServiceOptions.getDefaultProjectId(); + private static final String DEFAULT_CATALOG = + String.format("projects/%s/locations/global/catalogs/default_catalog", PROJECT_ID); + private static final Storage STORAGE = + StorageOptions.newBuilder().setProjectId(PROJECT_ID).build().getService(); + private static final String DEFAULT_BRANCH_NAME = + String.format("projects/%s/locations/global/catalogs/default_catalog/branches/0", PROJECT_ID); + + public static UserEvent getUserEvent(String visitorId) { + int value = 3; + + Timestamp timestamp = Timestamp.newBuilder().setSeconds(Instant.now().getEpochSecond()).build(); + + Product product = Product.newBuilder().setId(UUID.randomUUID().toString()).build(); + + ProductDetail productDetail = + ProductDetail.newBuilder() + .setProduct(product) + .setQuantity(Int32Value.newBuilder().setValue(value).build()) + .build(); + + UserEvent userEvent = + UserEvent.newBuilder() + .setEventType("detail-page-view") + .setVisitorId(visitorId) + .setEventTime(timestamp) + .addAllProductDetails(Collections.singletonList(productDetail)) + .build(); + System.out.println(userEvent); + + return userEvent; + } + + public static UserEvent writeUserEvent(String visitorId) 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 (UserEventServiceClient userEventServiceClient = UserEventServiceClient.create()) { + WriteUserEventRequest writeUserEventRequest = + WriteUserEventRequest.newBuilder() + .setUserEvent(getUserEvent(visitorId)) + .setParent(DEFAULT_CATALOG) + .build(); + + UserEvent userEvent = userEventServiceClient.writeUserEvent(writeUserEventRequest); + System.out.printf("The user event is written. %n%s%n", userEvent); + return userEvent; + } + } + + public static void purgeUserEvent(String visitorId) + 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 (UserEventServiceClient userEventServiceClient = UserEventServiceClient.create()) { + PurgeUserEventsRequest purgeUserEventsRequest = + PurgeUserEventsRequest.newBuilder() + .setFilter(String.format("visitorId=\"%s\"", visitorId)) + .setParent(DEFAULT_CATALOG) + .setForce(true) + .build(); + + OperationFuture purgeOperation = + userEventServiceClient.purgeUserEventsAsync(purgeUserEventsRequest); + System.out.printf("The purge operation was started: %s%n", purgeOperation.getName()); + } + } + + public static Product generateProduct() { + float price = 30.0f; + float originalPrice = 35.5f; + + PriceInfo priceInfo = + PriceInfo.newBuilder() + .setPrice(price) + .setOriginalPrice(originalPrice) + .setCurrencyCode("USD") + .build(); + + FulfillmentInfo fulfillmentInfo = + FulfillmentInfo.newBuilder() + .setType("pickup-in-store") + .addAllPlaceIds(Arrays.asList("store0", "store1")) + .build(); + + return Product.newBuilder() + .setTitle("Nest Mini") + .setType(Type.PRIMARY) + .addCategories("Speakers and displays") + .addBrands("Google") + .setPriceInfo(priceInfo) + .setAvailability(Availability.IN_STOCK) + .addFulfillmentInfo(fulfillmentInfo) + .build(); + } + + public static Product createProduct(String productId) throws IOException { + CreateProductRequest createProductRequest = + CreateProductRequest.newBuilder() + .setProduct(generateProduct()) + .setProductId(productId) + .setParent(DEFAULT_BRANCH_NAME) + .build(); + System.out.printf("Create product request: %s%n", createProductRequest); + + // 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 (ProductServiceClient serviceClient = ProductServiceClient.create()) { + Product createdProduct = serviceClient.createProduct(createProductRequest); + System.out.printf("Created product: %s%n", createdProduct); + return createdProduct; + } + } + + public static Product getProduct(String productName) throws IOException { + Product product = Product.newBuilder().build(); + + GetProductRequest getProductRequest = + GetProductRequest.newBuilder().setName(productName).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 (ProductServiceClient serviceClient = ProductServiceClient.create()) { + product = serviceClient.getProduct(getProductRequest); + System.out.println("Get product response: " + product); + return product; + } catch (NotFoundException e) { + System.out.printf("Product %s not found", productName); + return product; + } + } + + public static void deleteProduct(String productName) throws IOException { + DeleteProductRequest deleteProductRequest = + DeleteProductRequest.newBuilder().setName(productName).build(); + System.out.printf("Delete product request %s%n", deleteProductRequest); + + // 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 (ProductServiceClient serviceClient = ProductServiceClient.create()) { + serviceClient.deleteProduct(deleteProductRequest); + System.out.printf("Product %s was deleted.%n", productName); + } + } + + public static Bucket createBucket(String bucketName) { + if (checkIfBucketExists(bucketName)) { + System.out.printf("Bucket %s already exists. %n", bucketName); + Page bucketList = STORAGE.list(); + for (Bucket itrBucket : bucketList.iterateAll()) { + if (itrBucket.getName().equals(bucketName)) { + return itrBucket; + } + } + } + + System.out.printf("Creating new bucket: %s %n", bucketName); + + Bucket bucket = + STORAGE.create( + BucketInfo.newBuilder(bucketName).setStorageClass(STANDARD).setLocation("US").build()); + + System.out.println( + "Bucket was created " + + bucket.getName() + + " in " + + bucket.getLocation() + + " with storage class " + + bucket.getStorageClass()); + + return bucket; + } + + public static boolean checkIfBucketExists(String bucketToCheck) { + boolean bucketExists = false; + + Page bucketList = STORAGE.list(); + for (Bucket bucket : bucketList.iterateAll()) { + if (bucket.getName().equals(bucketToCheck)) { + bucketExists = true; + break; + } + } + + return bucketExists; + } + + public static void deleteBucket(String bucketName) { + try { + Bucket bucket = STORAGE.get(bucketName); + if (bucket != null) { + bucket.delete(); + } + } catch (StorageException e) { + System.out.printf("Bucket is not empty. Deleting objects from bucket.%n"); + deleteObjectsFromBucket(STORAGE.get(bucketName)); + System.out.printf("Bucket %s was deleted.%n", STORAGE.get(bucketName).getName()); + } + + if (STORAGE.get(bucketName) == null) { + System.out.printf("Bucket '%s' already deleted.%n", bucketName); + } + } + + public static void deleteObjectsFromBucket(Bucket bucket) { + Page blobs = bucket.list(); + for (Blob blob : blobs.iterateAll()) { + blob.delete(); + } + System.out.printf("All objects are deleted from GCS bucket %s%n", bucket.getName()); + } + + public static void uploadObject(String bucketName, String objectName, String filePath) + throws IOException { + BlobId blobId = BlobId.of(bucketName, objectName); + BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build(); + STORAGE.create(blobInfo, Files.readAllBytes(Paths.get(filePath))); + System.out.println( + "File " + filePath + " uploaded to bucket " + bucketName + " as " + objectName); + } + + public static void createBqDataset(String datasetName) { + try { + BigQuery bigquery = BigQueryOptions.getDefaultInstance().getService(); + DatasetInfo datasetInfo = DatasetInfo.newBuilder(datasetName).build(); + Dataset newDataset = bigquery.create(datasetInfo); + String newDatasetName = newDataset.getDatasetId().getDataset(); + System.out.printf("Dataset '%s' created successfully.%n", newDatasetName); + } catch (BigQueryException e) { + System.out.printf("Dataset '%s' already exists.%n", datasetName); + } + } + + public static void deleteDataset(String projectId, String datasetName) { + try { + BigQuery bigquery = BigQueryOptions.getDefaultInstance().getService(); + DatasetId datasetId = DatasetId.of(projectId, datasetName); + boolean success = bigquery.delete(datasetId, DatasetDeleteOption.deleteContents()); + if (success) { + System.out.printf("Dataset '%s' deleted successfully.%n", datasetName); + } + } catch (BigQueryException e) { + System.out.printf("Dataset '%s' was not found.%n", datasetName); + } + } + + public static void createBqTable(String datasetName, String tableName, Schema schema) { + try { + BigQuery bigquery = BigQueryOptions.getDefaultInstance().getService(); + TableId tableId = TableId.of(datasetName, tableName); + TableDefinition tableDefinition = StandardTableDefinition.of(schema); + TableInfo tableInfo = TableInfo.newBuilder(tableId, tableDefinition).build(); + bigquery.create(tableInfo); + System.out.printf("Table '%s' created successfully.%n", tableName); + } catch (BigQueryException e) { + System.out.printf("Table '%s' already exists.%n", tableName); + } + } + + public static void uploadDataToBqTable(String datasetName, String tableName, String sourceUri) { + try { + BigQuery bigquery = BigQueryOptions.getDefaultInstance().getService(); + TableId tableId = TableId.of(datasetName, tableName); + + WriteChannelConfiguration writeChannelConfiguration = + WriteChannelConfiguration.newBuilder(tableId) + .setFormatOptions(FormatOptions.json()) + .build(); + + String jobName = "jobId_" + UUID.randomUUID(); + JobId jobId = JobId.newBuilder().setLocation("us").setJob(jobName).build(); + + try (TableDataWriteChannel writer = bigquery.writer(jobId, writeChannelConfiguration); + OutputStream stream = Channels.newOutputStream(writer)) { + Files.copy(Paths.get(sourceUri), stream); + } + + Job job = bigquery.getJob(jobId); + Job completedJob = job.waitFor(); + if (job.isDone()) { + System.out.printf("Json successfully loaded in a table '%s'.%n", tableName); + } else { + System.out.println( + "BigQuery was unable to load into the table due to an error:" + + job.getStatus().getError()); + } + } catch (BigQueryException | InterruptedException e) { + System.out.printf("Column not added during load append: %s%n", e.getMessage()); + } catch (IOException e) { + System.out.printf("Error copying file: %s%n", e.getMessage()); + } + } + + public static Gson getGson() { + JsonDeserializer typeDeserializer = + (jsonElement, type, deserializationContext) -> { + return LegacySQLTypeName.valueOf(jsonElement.getAsString()); + }; + + JsonDeserializer subFieldsDeserializer = + (jsonElement, type, deserializationContext) -> { + Field[] fields = + deserializationContext.deserialize(jsonElement.getAsJsonArray(), Field[].class); + return FieldList.of(fields); + }; + + return new GsonBuilder() + .registerTypeAdapter(LegacySQLTypeName.class, typeDeserializer) + .registerTypeAdapter(FieldList.class, subFieldsDeserializer) + .create(); + } +} diff --git a/retail/interactive-tutorials/src/main/resources/events_schema.json b/retail/interactive-tutorials/src/main/resources/events_schema.json new file mode 100644 index 00000000000..a52c0e56f36 --- /dev/null +++ b/retail/interactive-tutorials/src/main/resources/events_schema.json @@ -0,0 +1,73 @@ +[ + { + "fields":[ + { + "mode": "NULLABLE", + "name": "currencyCode", + "type": "STRING" + }, + { + "mode": "NULLABLE", + "name": "revenue", + "type": "FLOAT" + } + ], + "mode": "NULLABLE", + "name": "purchaseTransaction", + "type": "RECORD" + }, + { + "fields":[ + { + "mode": "NULLABLE", + "name": "quantity", + "type": "INTEGER" + }, + { + "fields":[ + { + "mode": "NULLABLE", + "name": "id", + "type": "STRING" + } + ], + "mode": "NULLABLE", + "name": "product", + "type": "RECORD" + } + ], + "mode": "REPEATED", + "name": "productDetails", + "type": "RECORD" + }, + { + "mode": "REQUIRED", + "name": "eventTime", + "type": "STRING" + }, + { + "mode": "REQUIRED", + "name": "visitorId", + "type": "STRING" + }, + { + "mode": "REQUIRED", + "name": "eventType", + "type": "STRING" + }, + { + "mode": "NULLABLE", + "name": "searchQuery", + "type": "STRING" + }, + { + "mode": "NULLABLE", + "name": "cartId", + "type": "STRING" + }, + { + "mode": "REPEATED", + "name": "pageCategories", + "type": "STRING" + } + ] \ No newline at end of file diff --git a/retail/interactive-tutorials/src/main/resources/product_schema.json b/retail/interactive-tutorials/src/main/resources/product_schema.json new file mode 100644 index 00000000000..2dcc79f7fe3 --- /dev/null +++ b/retail/interactive-tutorials/src/main/resources/product_schema.json @@ -0,0 +1,317 @@ +[ + { + "name": "name", + "type": "STRING", + "mode": "NULLABLE" + }, + { + "name": "id", + "type": "STRING", + "mode": "REQUIRED" + }, + { + "name": "type", + "type": "STRING", + "mode": "NULLABLE" + }, + { + "name": "primaryProductId", + "type": "STRING", + "mode": "NULLABLE" + }, + { + "name": "collectionMemberIds", + "type": "STRING", + "mode": "REPEATED" + }, + { + "name": "gtin", + "type": "STRING", + "mode": "NULLABLE" + }, + { + "name": "categories", + "type": "STRING", + "mode": "REPEATED" + }, + { + "name": "title", + "type": "STRING", + "mode": "REQUIRED" + }, + { + "name": "brands", + "type": "STRING", + "mode": "REPEATED" + }, + { + "name": "description", + "type": "STRING", + "mode": "NULLABLE" + }, + { + "name": "languageCode", + "type": "STRING", + "mode": "NULLABLE" + }, + { + "name": "attributes", + "type": "RECORD", + "mode": "REPEATED", + "fields": [ + { + "name": "key", + "type": "STRING", + "mode": "NULLABLE" + }, + { + "name": "value", + "type": "RECORD", + "mode": "NULLABLE", + "fields": [ + { + "name": "text", + "type": "STRING", + "mode": "REPEATED" + }, + { + "name": "numbers", + "type": "FLOAT", + "mode": "REPEATED" + }, + { + "name": "searchable", + "type": "BOOLEAN", + "mode": "NULLABLE" + }, + { + "name": "indexable", + "type": "BOOLEAN", + "mode": "NULLABLE" + } + ] + } + ] + }, + { + "name": "tags", + "type": "STRING", + "mode": "REPEATED" + }, + { + "name": "priceInfo", + "type": "RECORD", + "mode": "NULLABLE", + "fields": [ + { + "name": "currencyCode", + "type": "STRING", + "mode": "NULLABLE" + }, + { + "name": "price", + "type": "FLOAT", + "mode": "NULLABLE" + }, + { + "name": "originalPrice", + "type": "FLOAT", + "mode": "NULLABLE" + }, + { + "name": "cost", + "type": "FLOAT", + "mode": "NULLABLE" + }, + { + "name": "priceEffectiveTime", + "type": "STRING", + "mode": "NULLABLE" + }, + { + "name": "priceExpireTime", + "type": "STRING", + "mode": "NULLABLE" + } + ] + }, + { + "name": "rating", + "type": "RECORD", + "mode": "NULLABLE", + "fields": [ + { + "name": "ratingCount", + "type": "INTEGER", + "mode": "NULLABLE" + }, + { + "name": "averageRating", + "type": "FLOAT", + "mode": "NULLABLE" + }, + { + "name": "ratingHistogram", + "type": "INTEGER", + "mode": "REPEATED" + } + ] + }, + { + "name": "expireTime", + "type": "STRING", + "mode": "NULLABLE" + }, + { + "name": "ttl", + "type": "RECORD", + "mode": "NULLABLE", + "fields": [ + { + "name": "seconds", + "type": "INTEGER", + "mode": "NULLABLE" + }, + { + "name": "nanos", + "type": "INTEGER", + "mode": "NULLABLE" + } + ] + }, + { + "name": "availableTime", + "type": "STRING", + "mode": "NULLABLE" + }, + { + "name": "availability", + "type": "STRING", + "mode": "NULLABLE" + }, + { + "name": "availableQuantity", + "type": "INTEGER", + "mode": "NULLABLE" + }, + { + "name": "fulfillmentInfo", + "type": "RECORD", + "mode": "REPEATED", + "fields": [ + { + "name": "type", + "type": "STRING", + "mode": "NULLABLE" + }, + { + "name": "placeIds", + "type": "STRING", + "mode": "REPEATED" + } + ] + }, + { + "name": "uri", + "type": "STRING", + "mode": "NULLABLE" + }, + { + "name": "images", + "type": "RECORD", + "mode": "REPEATED", + "fields": [ + { + "name": "uri", + "type": "STRING", + "mode": "REQUIRED" + }, + { + "name": "height", + "type": "INTEGER", + "mode": "NULLABLE" + }, + { + "name": "width", + "type": "INTEGER", + "mode": "NULLABLE" + } + ] + }, + { + "name": "audience", + "type": "RECORD", + "mode": "NULLABLE", + "fields": [ + { + "name": "genders", + "type": "STRING", + "mode": "REPEATED" + }, + { + "name": "ageGroups", + "type": "STRING", + "mode": "REPEATED" + } + ] + }, + { + "name": "colorInfo", + "type": "RECORD", + "mode": "NULLABLE", + "fields": [ + { + "name": "colorFamilies", + "type": "STRING", + "mode": "REPEATED" + }, + { + "name": "colors", + "type": "STRING", + "mode": "REPEATED" + } + ] + }, + { + "name": "sizes", + "type": "STRING", + "mode": "REPEATED" + }, + { + "name": "materials", + "type": "STRING", + "mode": "REPEATED" + }, + { + "name": "patterns", + "type": "STRING", + "mode": "REPEATED" + }, + { + "name": "conditions", + "type": "STRING", + "mode": "REPEATED" + }, + { + "name": "retrievableFields", + "type": "STRING", + "mode": "NULLABLE" + }, + { + "name": "publishTime", + "type": "STRING", + "mode": "NULLABLE" + }, + { + "name": "promotions", + "type": "RECORD", + "mode": "REPEATED", + "fields": [ + { + "name": "promotionId", + "type": "STRING", + "mode": "NULLABLE" + } + ] + } +] \ No newline at end of file diff --git a/retail/interactive-tutorials/src/main/resources/products.json b/retail/interactive-tutorials/src/main/resources/products.json new file mode 100644 index 00000000000..39dea765590 --- /dev/null +++ b/retail/interactive-tutorials/src/main/resources/products.json @@ -0,0 +1,316 @@ +{"id": "GGCOGOAC101259","name": "GGCOGOAC101259","title": "#IamRemarkable Pen","brands": ["#IamRemarkable"],"categories": ["Office"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "16"},"colorInfo": {"colorFamilies": ["Blue"],"colors": ["Light blue","Blue","Dark blue"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGCOGOAC101259.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Metal","Recycled Plastic"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Office/IamRemarkable+Pen"} +{"id": "GGOEAAEC172013","name": "GGOEAAEC172013","title": "Android Embroidered Crewneck Sweater","brands": ["Android"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"ecofriendly", "value": {"indexable": "false","searchable": "false","text": ["Low-impact fabrics","recycled fabrics","recycled packaging","plastic-free packaging","ethically made"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Android+Embroidered+Crewneck+Sweater"} +{"id": "GGPRAHPL107110","name": "GGPRAHPL107110","title": "Android Iconic Hat Green","brands": ["Android"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "16"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive","Grass green","Light green"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEAHPL130910.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Android+Iconic+Hat+Green"} +{"id": "GGOEAAKQ137410","name": "GGOEAAKQ137410","title": "Android Iconic Sock","brands": ["Android"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "17"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEAAKQ137410.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Membrane"]}},{"key":"ecofriendly", "value": {"indexable": "false","searchable": "false","text": ["Low-impact fabrics","recycled fabrics","recycled packaging","plastic-free packaging","ethically made"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Android+Iconic+Sock"} +{"id": "GGOEAAWL130147","name": "GGOEAAWL130147","title": "Android Pocket Onesie Navy","brands": ["Android"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEAXXX1301.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Membrane"]}},{"key":"ecofriendly", "value": {"indexable": "false","searchable": "false","text": ["Low-impact fabrics","recycled fabrics","recycled packaging","plastic-free packaging","ethically made"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Android+Pocket+Onesie+Navy"} +{"id": "GGOEGAED142617","name": "GGOEGAED142617","title": "Google Austin Campus Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1426.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Austin+Campus+Unisex+Tee"} +{"id": "GGOEGAEJ163316","name": "GGOEGAEJ163316","title": "Google Charcoal Unisex Badge Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "21"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1633.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino","Membrane"]}},{"key":"ecofriendly", "value": {"indexable": "false","searchable": "false","text": ["Low-impact fabrics","recycled fabrics","recycled packaging","plastic-free packaging","ethically made"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Charcoal+Unisex+Badge+Tee"} +{"id": "GGOEGDWC140899","name": "GGOEGDWC140899","title": "Google Chicago Campus Mug","brands": ["Google"],"categories": ["Drinkware"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "12"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGDWC140899.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Drinkware/Google+Chicago+Campus+Mug"} +{"id": "GGOEGCBD142299","name": "GGOEGCBD142299","title": "Google Cork Tablet Case","brands": ["Google"],"categories": ["Bags"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGCBD142299.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Accessories/Google+Cork+Tablet+Case"} +{"id": "GGOEGAEB119414","name": "GGOEGAEB119414","title": "Google Dino Game Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1194.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino","Membrane"]}},{"key":"ecofriendly", "value": {"indexable": "false","searchable": "false","text": ["Low-impact fabrics","recycled fabrics","recycled packaging","plastic-free packaging","ethically made"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Dino+Game+Tee"} +{"id": "GGOEGAAH134316","name": "GGOEGAAH134316","title": "Google Heather Green Speckled Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive","Grass green","Light green"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1343.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino","Membrane"]}},{"key":"ecofriendly", "value": {"indexable": "false","searchable": "false","text": ["Low-impact fabrics","recycled fabrics","recycled packaging","plastic-free packaging","ethically made"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Heather+Green+Speckled+Tee"} +{"id": "GGPRGBRC104499","name": "GGPRGBRC104499","title": "Google Incognito Zippack V2","brands": ["Google"],"categories": ["Bags"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "36"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGBRC128099.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Google+Incognito+Zippack+V2"} +{"id": "GGOEGAEH146017","name": "GGOEGAEH146017","title": "Google LA Campus Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1460.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"ecofriendly", "value": {"indexable": "false","searchable": "false","text": ["Low-impact fabrics","recycled fabrics","recycled packaging","plastic-free packaging","ethically made"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+LA+Campus+Unisex+Tee"} +{"id": "GGOEGAED161612","name": "GGOEGAED161612","title": "Google LA Campus Women Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGCOGXXX1569.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino","Membrane"]}},{"key":"ecofriendly", "value": {"indexable": "false","searchable": "false","text": ["Low-impact fabrics","recycled fabrics","recycled packaging","plastic-free packaging","ethically made"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Land+and+Sea+Unisex+Tee+LS"} +{"id": "GGOEGCBA150799","name": "GGOEGCBA150799","title": "Google Large Pet Leash (Red/Yellow)","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGCBA150799.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Accessories/Google+Large+Pet+Leash+Red+Yellow"} +{"id": "GGOEGADJ137115","name": "GGOEGADJ137115","title": "Google Men's Tech Fleece Vest Charcoal","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "39"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1371.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino","Membrane"]}},{"key":"ecofriendly", "value": {"indexable": "false","searchable": "false","text": ["Low-impact fabrics","recycled fabrics","recycled packaging","plastic-free packaging","ethically made"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Mens+Tech+Fleece+Vest+Charcoal"} +{"id": "GGOEGAER119515","name": "GGOEGAER119515","title": "Google Mountain View Tee Red","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"colorInfo": {"colorFamilies": ["Red"],"colors": ["Red","Neon red"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1195.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"ecofriendly", "value": {"indexable": "false","searchable": "false","text": ["Low-impact fabrics","recycled fabrics","recycled packaging","plastic-free packaging","ethically made"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Mountain+View+Tee+Red"} +{"id": "GGOEGAEB140413","name": "GGOEGAEB140413","title": "Google NYC Campus Zip Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "38"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1404.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino","Membrane"]}},{"key":"ecofriendly", "value": {"indexable": "false","searchable": "false","text": ["Low-impact fabrics","recycled fabrics","recycled packaging","plastic-free packaging","ethically made"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+NYC+Campus+Zip+Hoodie"} +{"id": "GGOEGAEC165215","name": "GGOEGAEC165215","title": "Google Navy French Terry Zip Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1652.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"ecofriendly", "value": {"indexable": "false","searchable": "false","text": ["Low-impact fabrics","recycled fabrics","recycled packaging","plastic-free packaging","ethically made"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Navy+French+Terry+Zip+Hoodie"} +{"id": "GGOEGALJ148813","name": "GGOEGALJ148813","title": "Google Seattle Campus Ladies Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1488.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"ecofriendly", "value": {"indexable": "false","searchable": "false","text": ["Low-impact fabrics","recycled fabrics","recycled packaging","plastic-free packaging","ethically made"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Seattle+Campus+Ladies+Tee"} +{"id": "GGOEGALJ148816","name": "GGOEGALJ148816","title": "Google Seattle Campus Ladies Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1488.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino","Membrane"]}},{"key":"ecofriendly", "value": {"indexable": "false","searchable": "false","text": ["Low-impact fabrics","recycled fabrics","recycled packaging","plastic-free packaging","ethically made"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Seattle+Campus+Ladies+Tee"} +{"id": "GGOEGAAQ117715","name": "GGOEGAAQ117715","title": "Google Striped Tank","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "29"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1177.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"ecofriendly", "value": {"indexable": "false","searchable": "false","text": ["Low-impact fabrics","recycled fabrics","recycled packaging","plastic-free packaging","ethically made"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Google+Striped+Tank"} +{"id": "GGOEGAAQ117716","name": "GGOEGAAQ117716","title": "Google Striped Tank","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "29"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1177.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino","Membrane"]}},{"key":"ecofriendly", "value": {"indexable": "false","searchable": "false","text": ["Low-impact fabrics","recycled fabrics","recycled packaging","plastic-free packaging","ethically made"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Google+Striped+Tank"} +{"id": "GGCOGAEJ153718","name": "GGCOGAEJ153718","title": "Google TYCTWD Gray Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1537.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/TYCTWD/Google+TYCTWD+Charcoal+Tee"} +{"id": "GGOEGAER090417","name": "GGOEGAER090417","title": "Google Tee Red","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["Red"],"colors": ["Red","Flame red"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX0904.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Membrane"]}},{"key":"ecofriendly", "value": {"indexable": "false","searchable": "false","text": ["Low-impact fabrics","recycled fabrics","recycled packaging","plastic-free packaging","ethically made"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Tee+Red"} +{"id": "GGOEGAXB135628","name": "GGOEGAXB135628","title": "Google Toddler Hero Tee Charcoal Black","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "24"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Ebony","Outer Space","Jet"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1356.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"ecofriendly", "value": {"indexable": "false","searchable": "false","text": ["Low-impact fabrics","recycled fabrics","recycled packaging","plastic-free packaging","ethically made"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Toddler+Hero+Tee+Black"} +{"id": "GGOEGHBJ101899","name": "GGOEGHBJ101899","title": "Google Twill Cap Charcoal","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "13"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGHBJ101899.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Twill+Cap+Charcoal"} +{"id": "GGOEGAEB125316","name": "GGOEGAEB125316","title": "Google Unisex Pride Eco-Tee Black","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Onyx","Ebony","Outer Space","Jet"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1253.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino","Membrane"]}},{"key":"ecofriendly", "value": {"indexable": "false","searchable": "false","text": ["Low-impact fabrics","recycled fabrics","recycled packaging","plastic-free packaging","ethically made"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Unisex+Pride+Eco-Tee+Black"} +{"id": "GGOEGAEB170917","name": "GGOEGAEB170917","title": "Google Unisex V-neck Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "27"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1709.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Unisex+V+neck+Tee"} +{"id": "GGOEGAPC167099","name": "GGOEGAPC167099","title": "Google Vintage Cap Navy","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "3"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGAPC167099.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Vintage+Cap+Navy"} +{"id": "GGOEGAEH174914","name": "GGOEGAEH174914","title": "Google Vintage Olive Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "28"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1749.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Vintage+Olive+Tee"} +{"id": "GGOEGAPJ108213","name": "GGOEGAPJ108213","title": "Google Women's Discovery Lt. Rain Shell","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "38"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1082.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Womens+Discovery"} +{"id": "GGOEGALB119017","name": "GGOEGALB119017","title": "Google Women's Eco Tee Black","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Onyx","Ebony","Outer Space","Jet"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1190.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"ecofriendly", "value": {"indexable": "false","searchable": "false","text": ["Low-impact fabrics","recycled fabrics","recycled packaging","plastic-free packaging","ethically made"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Womens+Eco+Tee+Black"} +{"id": "GGOEGAWH126845","name": "GGOEGAWH126845","title": "Stan and Friends 2019 Onesie","brands": ["Stan and Friends"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive","Grass green","Light green"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1268.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Stan+and+Friends+Onesie+Green"} +{"id": "GGOEYOCR125599","name": "GGOEYOCR125599","title": "YouTube Transmission Journal Red","brands": ["YouTube"],"categories": ["Office"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "16"},"colorInfo": {"colorFamilies": ["Red"],"colors": ["Red","Neon red","Flame red"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEYOCR125599.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/YouTube+Transmission+Journal+Red"} +{"id": "GGCOGADC100815","name": "GGCOGADC100815","title": "#IamRemarkable Hoodie","brands": ["#IamRemarkable"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "32"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGCOGXXX1008.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/IamRemarkable+Hoodie"} +{"id": "GGCOGALC100713","name": "GGCOGALC100713","title": "#IamRemarkable Ladies T-Shirt","brands": ["#IamRemarkable"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "12"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGCOGXXX1007.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/IamRemarkable+Ladies+T-Shirt"} +{"id": "GGOEAAYH130212","name": "GGOEAAYH130212","title": "Android Pocket Youth Tee Green","brands": ["Android"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive","Grass green","Light green"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEAXXX1302.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Android+Pocket+Youth+Tee+Green"} +{"id": "GGPRGCBA104199","name": "GGPRGCBA104199","title": "Google ApPeel Journal Red","brands": ["Google"],"categories": ["Office"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "3.67"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20210405858/assets/items/images/GGPRGCBA104199.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Google+Sustainable+Kit"} +{"id": "GGOEGAFB134012","name": "GGOEGAFB134012","title": "Google Badge Heavyweight Pullover Black","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "38"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Onyx","Ebony","Outer Space","Jet"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1340.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Badge+Heavyweight+Pullover+Black"} +{"id": "GGOEGAFB134018","name": "GGOEGAFB134018","title": "Google Badge Heavyweight Pullover Black","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "38"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Onyx","Outer Space","Jet"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1340.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Badge+Heavyweight+Pullover+Black"} +{"id": "GGOEGALL144015","name": "GGOEGALL144015","title": "Google Boulder Campus Ladies Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1440.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Boulder+Campus+Ladies+Tee"} +{"id": "GGOEGADH120418","name": "GGOEGADH120418","title": "Google Campus Raincoat Green","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "44"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive","Grass green","Light green"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Campus+Raincoat+Green"} +{"id": "GGOEGBJD141499","name": "GGOEGBJD141499","title": "Google Chicago Campus Tote","brands": ["Google"],"categories": ["Bags"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "11"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGBJD141499.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Bags/Google+Chicago+Campus+Tote"} +{"id": "GGPRGDHB106099","name": "GGPRGDHB106099","title": "Google Chrome Dino Light Up Water Bottle","brands": ["Google"],"categories": ["Drinkware"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "24"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGDHB163199.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Google+Chrome+Dino+Light+Up+Water+Bottle"} +{"id": "GGOEGAEB173714","name": "GGOEGAEB173714","title": "Google Crewneck Sweatshirt Black","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "37"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Onyx","Outer Space","Jet"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Crewneck+Sweatshirt+Black"} +{"id": "GGOEGADH134214","name": "GGOEGADH134214","title": "Google Crewneck Sweatshirt Green","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive","Grass green","Light green"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1342.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Crewneck+Sweatshirt+Green"} +{"id": "GGOEGAER149217","name": "GGOEGAER149217","title": "Google Kirkland Campus Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1492.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Kirkland+Campus+Unisex+Tee"} +{"id": "GGOEGOAA172399","name": "GGOEGOAA172399","title": "Google Ombre Pen","brands": ["Google"],"categories": ["Office"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "1.75"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGOAA172399.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Ombre+Pen+Yellow"} +{"id": "GGOEGAEJ148013","name": "GGOEGAEJ148013","title": "Google PNW Campus Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1480.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+PNW+Campus+Zip+Hoodie"} +{"id": "GGOEGAEJ148214","name": "GGOEGAEJ148214","title": "Google PNW Campus Zip Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "38"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1482.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+PNW+Campus+Unisex+Tee"} +{"id": "GGPRGAAB100712","name": "GGPRGAAB100712","title": "Google Unisex Eco Tee Black","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Ebony","Outer Space","Jet"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20210405858/assets/items/images/GGPRGXXX1007.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Google+Unisex+Eco+Tee+Black"} +{"id": "GGOEGAQB107813","name": "GGOEGAQB107813","title": "Google Women's Grid Zip-Up","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "33"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1078.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Womens+Grid+Zip+Up"} +{"id": "GGOEGAPB176914","name": "GGOEGAPB176914","title": "Google Women's Puffer Jacket","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "36"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Womens+Puffer+Jacket"} +{"id": "GGOEGATB176713","name": "GGOEGATB176713","title": "Google Women's Puffer Vest","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "34"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Womens+Puffer+Vest"} +{"id": "GGOEGAPJ138615","name": "GGOEGAPJ138615","title": "Google Women's Tech Fleece Grey","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "39"},"colorInfo": {"colorFamilies": ["Gray"],"colors": ["Light gray","Silver"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1386.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Womens+Tech+Fleece+Grey"} +{"id": "GGOEGAYH135914","name": "GGOEGAYH135914","title": "Google Youth Badge Tee Olive","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "24"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1359.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Youth+Badge+Tee+Olive"} +{"id": "GGOEGAYB113113","name": "GGOEGAYB113113","title": "Google Youth FC Longsleeve Charcoal","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1131.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Youth+FC+Longsleeve+Charcoal"} +{"id": "GGOEGAEH126718","name": "GGOEGAEH126718","title": "Stan and Friends 2019 Tee","brands": ["Stan and Friends"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive","Grass green"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1267.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Stan+and+Friends+Tee+Green"} +{"id": "GGOEYAEB093815","name": "GGOEYAEB093815","title": "YouTube Icon Pullover Black","brands": ["YouTube"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Ebony","Outer Space","Jet"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEYXXX0938.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/YouTube+Icon+Hoodie+Black"} +{"id": "GGOEYAEJ120318","name": "GGOEYAEJ120318","title": "YouTube Icon Tee Grey","brands": ["YouTube"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["Gray"],"colors": ["Light gray","Silver"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEYXXX1203.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/YouTube+Icon+Tee+Grey"} +{"id": "GGPRAOAL107699","name": "GGPRAOAL107699","title": "Android Iconic Pen","brands": ["Android"],"categories": ["Office"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "1.75"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEAOAL129199.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Android+Iconic+Pen"} +{"id": "GGOEAAEH129617","name": "GGOEAAEH129617","title": "Android Pocket Tee Green","brands": ["Android"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "29"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive","Grass green"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEAXXX1296.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Android+Pocket+Tee+Green"} +{"id": "GGPRAAEH107217","name": "GGPRAAEH107217","title": "Android Pocket Tee Green","brands": ["Android"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "29"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive","Grass green"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEAXXX1296.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Android+Pocket+Tee+Green"} +{"id": "GGOEAAXL129928","name": "GGOEAAXL129928","title": "Android Pocket Toddler Tee Navy","brands": ["Android"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "23"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEAXXX1299.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Android+Pocket+Toddler+Tee+Navy"} +{"id": "GGOEGAEC171813","name": "GGOEGAEC171813","title": "Google Bike Eco Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1718.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Bike+Eco+Tee"} +{"id": "GGOEGALL144016","name": "GGOEGALL144016","title": "Google Boulder Campus Ladies Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1440.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Boulder+Campus+Ladies+Tee"} +{"id": "GGOEGAEC176213","name": "GGOEGAEC176213","title": "Google Camp Fleece Snap Pullover","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "32"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Camp+Fleece+Snap+Pullover"} +{"id": "GGOEGAER141014","name": "GGOEGAER141014","title": "Google Chicago Campus Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1410.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Chicago+Campus+Unisex+Tee"} +{"id": "GGOEGAEJ096415","name": "GGOEGAEJ096415","title": "Google Crewneck Sweatshirt Grey","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"colorInfo": {"colorFamilies": ["Gray"],"colors": ["Light gray","Silver"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX0964.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Crew+Grey"} +{"id": "GGOEGCBA139099","name": "GGOEGCBA139099","title": "Google Emoji Magnet Set","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "10"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGCBA139099.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Accessories/Google+Emoji+Magnet+Set"} +{"id": "GGOEGBRC127999","name": "GGOEGBRC127999","title": "Google Incognito Techpack V2","brands": ["Google"],"categories": ["Bags"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "38"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGBRC127999.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Bags/Google+Incognito+Techpack+V2"} +{"id": "GGPRGCBA105199","name": "GGPRGCBA105199","title": "Google Medium Pet Collar (Blue/Green)","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"colorInfo": {"colorFamilies": ["Blue"],"colors": ["Light blue","Blue","Green blue"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGCBA139599.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Google+Medium+Pet+Collar+Blue+Green"} +{"id": "GGOEGAER119516","name": "GGOEGAER119516","title": "Google Mountain View Tee Red","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"colorInfo": {"colorFamilies": ["Red"],"colors": ["Red","Flame red","Dark red"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1195.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Mountain+View+Tee+Red"} +{"id": "GGOEGAEJ148014","name": "GGOEGAEJ148014","title": "Google PNW Campus Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1480.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+PNW+Campus+Zip+Hoodie"} +{"id": "GGPRGOAH102499","name": "GGPRGOAH102499","title": "Google Pen Citron","brands": ["Google"],"categories": ["Office"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "1.75"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20210405858/assets/items/images/GGPRGOAH102499.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Google+Pen+Citron"} +{"id": "GGOEGAEL146914","name": "GGOEGAEL146914","title": "Google SF Campus Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1469.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+SF+Campus+Unisex+Tee"} +{"id": "GGCOGALB153913","name": "GGCOGALB153913","title": "Google TYCTWD Black Women's Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1539.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/TYCTWD/Google+TYCTWD+Womens+Tee"} +{"id": "GGCOGAEJ153715","name": "GGCOGAEJ153715","title": "Google TYCTWD Gray Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1537.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/TYCTWD/Google+TYCTWD+Charcoal+Tee"} +{"id": "GGOEGAEC173816","name": "GGOEGAEC173816","title": "Google Tonal Shirt Marine Blue","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "27"},"colorInfo": {"colorFamilies": ["Blue"],"colors": ["Light blue","Blue","Dark blue"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Tonal+Shirt+Marine+Blue"} +{"id": "GGOEGAEJ104015","name": "GGOEGAEJ104015","title": "Google Tudes Recycled Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1040.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Tudes+Recycled+Tee"} +{"id": "GGPRGAAB100718","name": "GGPRGAAB100718","title": "Google Unisex Eco Tee Black","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Onyx","Ebony","Outer Space"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20210405858/assets/items/images/GGPRGXXX1007.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Google+Unisex+Eco+Tee+Black"} +{"id": "GGOEGAPH138213","name": "GGOEGAPH138213","title": "Google Women's Softshell Moss","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "39"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1382.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Womens+Softshell+Moss"} +{"id": "GGOEGAYB113713","name": "GGOEGAYB113713","title": "Google Youth FC Zip Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1137.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Youth+FC+Zip+Hoodie"} +{"id": "GGOEGAEB110915","name": "GGOEGAEB110915","title": "Google Zip Hoodie F/C","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1109.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Zip+Hoodie+FC"} +{"id": "GGPRACBA107016","name": "GGPRACBA107016","title": "I \u003c3 Android Kit","brands": ["Android"],"categories": ["Kit"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "44.75"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20210405858/assets/items/images/GGPRAXXX1070.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/I+Love+Android+Kit"} +{"id": "GGOEYAEJ120313","name": "GGOEYAEJ120313","title": "YouTube Icon Tee Grey","brands": ["YouTube"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["Gray"],"colors": ["Light gray","Silver"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEYXXX1203.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/YouTube+Icon+Tee+Grey"} +{"id": "GGOEYADJ173418","name": "GGOEYADJ173418","title": "YouTube Ultralight Embroidered Sweatshirt","brands": ["YouTube"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "33"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/YouTube+Ultralight+Embroidered+Sweatshirt"} +{"id": "GGOEAFDH105799","name": "GGOEAFDH105799","title": "Android Cardboard Sculpture","brands": ["Android"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "17"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEAFDH105799.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Accessories/Android+Cardboard+Sculpture"} +{"id": "GGOEGAEM126414","name": "GGOEGAEM126414","title": "Android Garden 2019 Tee","brands": ["Android"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "29"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1264.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Android+Garden+Tee+Orange"} +{"id": "GGOEAFBA115599","name": "GGOEAFBA115599","title": "Google Android Super Hero 3D Framed Art","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEAFBA115599.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Accessories/Android+Super+Hero+3D+Framed+Art"} +{"id": "GGOECAEB163614","name": "GGOECAEB163614","title": "Google Black Cloud Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "28"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Onyx","Ebony","Jet"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOECXXX1636.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Black+Cloud+Tee"} +{"id": "GGOEGAEH143916","name": "GGOEGAEH143916","title": "Google Boulder Campus Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1439.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Boulder+Campus+Unisex+Tee"} +{"id": "GGOEGAEJ133717","name": "GGOEGAEJ133717","title": "Google Cambridge Campus Zip Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "38"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1337.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Cambridge+Campus+Zip+Hoodie"} +{"id": "GGOEGAEJ168612","name": "GGOEGAEJ168612","title": "Google Campus Unisex Zip Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1686.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Campus+Unisex+Zip+Hoodie"} +{"id": "GGOEGAEJ168613","name": "GGOEGAEJ168613","title": "Google Campus Unisex Zip Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1686.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Campus+Unisex+Zip+Hoodie"} +{"id": "GGPRGCBD102699","name": "GGPRGCBD102699","title": "Google Cork Tablet Case","brands": ["Google"],"categories": ["Bags"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20210405858/assets/items/images/GGPRGCBD102699.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Google+Cork+Tablet+Case"} +{"id": "GGOEGAER149212","name": "GGOEGAER149212","title": "Google Kirkland Campus Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1492.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Kirkland+Campus+Unisex+Tee"} +{"id": "GGOEGCBA162099","name": "GGOEGCBA162099","title": "Google Land Sea Tech Taco","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "3"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGCOGCBA161199.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Land+and+Sea+Tech+Taco+LS"} +{"id": "GGOEGALC153213","name": "GGOEGALC153213","title": "Google Mountain View Campus Ladies Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1532.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Campus+Collection/Google+Mountain+View+Campus+Ladies+Tee"} +{"id": "GGOEGAEH153016","name": "GGOEGAEH153016","title": "Google Mountain View Campus Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1530.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Campus+Collection/Google+Mountain+View+Campus+Unisex+Tee"} +{"id": "GGOEGAEJ153416","name": "GGOEGAEJ153416","title": "Google Mountain View Campus Zip Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "38"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1534.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Campus+Collection/Google+Mountain+View+Campus+Zip+Hoodie"} +{"id": "GGOEGAEJ140215","name": "GGOEGAEJ140215","title": "Google NYC Campus Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1402.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+NYC+Campus+Unisex+Tee"} +{"id": "GGOEGBBA175499","name": "GGOEGBBA175499","title": "Google Recycled Drawstring Bag","brands": ["Google"],"categories": ["Bags"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "3"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Recycled+Drawstring+Bag"} +{"id": "GGOEGALL147017","name": "GGOEGALL147017","title": "Google SF Campus Ladies Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1470.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+SF+Campus+Ladies+Tee"} +{"id": "GGOEGALJ148814","name": "GGOEGALJ148814","title": "Google Seattle Campus Ladies Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1488.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Seattle+Campus+Ladies+Tee"} +{"id": "GGOEGAEH148718","name": "GGOEGAEH148718","title": "Google Seattle Campus Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1487.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Seattle+Campus+Unisex+Tee"} +{"id": "GGOEGAEJ153514","name": "GGOEGAEJ153514","title": "Google Sunnyvale Campus Zip Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "38"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1535.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Campus+Collection/Google+Sunnyvale+Campus+Zip+Hoodie"} +{"id": "GGOEGAED176313","name": "GGOEGAED176313","title": "Google Sweatshirt Brick Red","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"colorInfo": {"colorFamilies": ["Red"],"colors": ["Red","Flame red","Dark red"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Sweatshirt+Brick+Red"} +{"id": "GGOEGAEC090714","name": "GGOEGAEC090714","title": "Google Tee Blue","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["Blue"],"colors": ["Light blue","Blue","Dark blue"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX0907.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Tee+Blue"} +{"id": "GGOEGAAB118913","name": "GGOEGAAB118913","title": "Google Unisex Eco Tee Black","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Onyx","Ebony","Jet"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1189.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Unisex+Eco+Tee+Black"} +{"id": "GGOEGAPB176915","name": "GGOEGAPB176915","title": "Google Women's Puffer Jacket","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "36"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Womens+Puffer+Jacket"} +{"id": "GGOEGAEB110912","name": "GGOEGAEB110912","title": "Google Zip Hoodie F/C","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1109.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Zip+Hoodie+FC"} +{"id": "GGPRACBA107018","name": "GGPRACBA107018","title": "I \u003c3 Android Kit","brands": ["Android"],"categories": ["Kit"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "44.75"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20210405858/assets/items/images/GGPRAXXX1070.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/I+Love+Android+Kit"} +{"id": "GGOEYAEA105610","name": "GGOEYAEA105610","title": "YouTube Crew Socks","brands": ["YouTube"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "16"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEYAEA105610.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/YouTube+Crew+Socks"} +{"id": "GGCOGADC100817","name": "GGCOGADC100817","title": "#IamRemarkable Hoodie","brands": ["#IamRemarkable"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "32"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGCOGXXX1008.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/IamRemarkable+Hoodie"} +{"id": "GGCOGAEC100613","name": "GGCOGAEC100613","title": "#IamRemarkable T-Shirt","brands": ["#IamRemarkable"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "12"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGCOGXXX1006.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/IamRemarkable+Unisex+T-Shirt"} +{"id": "GGOEGCKR133899","name": "GGOEGCKR133899","title": "Google Cambridge Campus Sticker","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "2"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGCKR133899.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Accessories/Google+Cambridge+Campus+Sticker"} +{"id": "GGOEGAEJ168615","name": "GGOEGAEJ168615","title": "Google Campus Unisex Zip Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1686.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Campus+Unisex+Zip+Hoodie"} +{"id": "GGOEGAER141013","name": "GGOEGAER141013","title": "Google Chicago Campus Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1410.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Chicago+Campus+Unisex+Tee"} +{"id": "GGOECOLJ164299","name": "GGOECOLJ164299","title": "Google Cloud Journal","brands": ["Google Cloud"],"categories": ["Office"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "18"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOECOLJ164299.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Stationery/Google+Cloud+Journal"} +{"id": "GGOEGHPB178810","name": "GGOEGHPB178810","title": "Google Corduroy Black Cap","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "19"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Onyx","Ebony","Outer Space","Jet"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Corduroy+Black+Cap"} +{"id": "GGOEGAXA123610","name": "GGOEGAXA123610","title": "Google Crew Combed Cotton Sock","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "17"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGAXA123610.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Google+Crew+Combed+Cotton+Sock"} +{"id": "GGOEGAXA123510","name": "GGOEGAXA123510","title": "Google Crew Striped Athletic Sock","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "17"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGAXA123510.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Google+Crew+Striped+Athletic+Sock"} +{"id": "GGOEGAEJ103915","name": "GGOEGAEJ103915","title": "Google F/C Longsleeve Ash","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1039.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+FC+Longsleeve+Ash"} +{"id": "GGOEGAEJ165013","name": "GGOEGAEJ165013","title": "Google Gray French Terry Sweatshirt","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"colorInfo": {"colorFamilies": ["Gray"],"colors": ["Light gray","Silver"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1650.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Gray+French+Terry+Sweatshirt"} +{"id": "GGOEGAXJ164914","name": "GGOEGAXJ164914","title": "Google Gray Toddler Zip Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"colorInfo": {"colorFamilies": ["Gray"],"colors": ["Light gray","Silver","Stone gray","Cool gray"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1649.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Gray+Toddler+Zip+Hoodie"} +{"id": "GGOEGCBA169499","name": "GGOEGCBA169499","title": "Google Kirkland Campus Patch Set","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "16"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGCBA169499.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Campus+Collection/Google+Kirkland+Campus+Patch+Set"} +{"id": "GGOEGCBA150599","name": "GGOEGCBA150599","title": "Google Large Pet Collar (Red/Yellow)","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGCBA150599.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Accessories/Google+Large+Pet+Collar+Red+Yellow"} +{"id": "GGOEGOAH090199","name": "GGOEGOAH090199","title": "Google Light Pen Green","brands": ["Google"],"categories": ["Office"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "3"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive","Grass green"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGOAH090199.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Metal","Recycled Plastic"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Office/Google+Light+Up+Pen+Green"} +{"id": "GGOEGCBA169399","name": "GGOEGCBA169399","title": "Google Los Angeles Campus Patch Set","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "16"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGCBA169399.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Campus+Collection/Google+Los+Angeles+Campus+Patch+Set"} +{"id": "GGOEGOAB177399","name": "GGOEGOAB177399","title": "Google Maps Wheat Pen","brands": ["Google"],"categories": ["Office"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "1.75"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGOAB177399.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Maps+Wheat+Pen"} +{"id": "GGOEGAEH153018","name": "GGOEGAEH153018","title": "Google Mountain View Campus Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1530.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Campus+Collection/Google+Mountain+View+Campus+Unisex+Tee"} +{"id": "GGOEGALJ140315","name": "GGOEGALJ140315","title": "Google NYC Campus Ladies Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1403.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+NYC+Campus+Ladies+Tee"} +{"id": "GGOEGAEC165218","name": "GGOEGAEC165218","title": "Google Navy French Terry Zip Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1652.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Navy+French+Terry+Zip+Hoodie"} +{"id": "GGPRGADC107914","name": "GGPRGADC107914","title": "Google Raincoat Navy","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "44"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1350.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Google+Raincoat+Navy"} +{"id": "GGOEGALJ148815","name": "GGOEGALJ148815","title": "Google Seattle Campus Ladies Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1488.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Seattle+Campus+Ladies+Tee"} +{"id": "GGOEGADJ135212","name": "GGOEGADJ135212","title": "Google Sherpa Zip Hoodie Charcoal","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "39"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1352.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Sherpa+Zip+Hoodie+Charcoal"} +{"id": "GGCOGAYC154115","name": "GGCOGAYC154115","title": "Google TYCTWD Blue Youth Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "24"},"colorInfo": {"colorFamilies": ["Blue"],"colors": ["Light blue","Blue","Dark blue"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1541.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/TYCTWD/Google+TYCTWD+Blue+Youth+Tee"} +{"id": "GGOEGAEC164713","name": "GGOEGAEC164713","title": "Google Tonal Blue Eco Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "27"},"colorInfo": {"colorFamilies": ["Blue"],"colors": ["Light blue","Blue","Dark blue"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1647.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Tonal+Blue+Eco+Tee"} +{"id": "GGOEGHPL107710","name": "GGOEGHPL107710","title": "Google Twill Cap Navy","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "13"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy","Dark blue"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGHPL107710.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Dad+Hat+Navy"} +{"id": "GGPRGAAB100714","name": "GGPRGAAB100714","title": "Google Unisex Eco Tee Black","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Onyx","Ebony","Outer Space","Jet"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20210405858/assets/items/images/GGPRGXXX1007.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Google+Unisex+Eco+Tee+Black"} +{"id": "GGOEGAEH174912","name": "GGOEGAEH174912","title": "Google Vintage Olive Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "28"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1749.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Vintage+Olive+Tee"} +{"id": "GGOEGAEH175114","name": "GGOEGAEH175114","title": "Google Vintage Pullover Olive","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Vintage+Pullover+Olive"} +{"id": "GGOEAAXQ129830","name": "GGOEAAXQ129830","title": "Android Pocket Toddler Tee White","brands": ["Android"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "23"},"colorInfo": {"colorFamilies": ["White"],"colors": ["White"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEAXXX1298.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Android+Pocket+Toddler+Tee+White"} +{"id": "GGOEGBJC122399","name": "GGOEGBJC122399","title": "Google Campus Bike Tote Navy","brands": ["Google"],"categories": ["Bags"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "11"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy","Dark blue"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGBJC122399.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Bags/Google+Google+Campus+Bike+Tote+Navy"} +{"id": "GGOEGAEC141216","name": "GGOEGAEC141216","title": "Google Chicago Campus Zip Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "38"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1412.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Chicago+Campus+Zip+Hoodie"} +{"id": "GGOEGAEA137817","name": "GGOEGAEA137817","title": "Google Cotopaxi Shell","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1378.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Cotopaxi+Shell"} +{"id": "GGOEGAED168116","name": "GGOEGAED168116","title": "Google Earth Day Eco Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1681.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Earth+Day+Eco+Tee"} +{"id": "GGOEGAEJ165116","name": "GGOEGAEJ165116","title": "Google Gray French Terry Zip Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"colorInfo": {"colorFamilies": ["Gray"],"colors": ["Light gray","Silver","Stone gray","Cool gray"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1651.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Gray+French+Terry+Zip+Hoodie"} +{"id": "GGPRGBRC101599","name": "GGPRGBRC101599","title": "Google Incognito Laptop Organizer V2","brands": ["Google"],"categories": ["Bags"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "36"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20210405858/assets/items/images/GGPRGBRC101599.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Google+Incognito+Laptop+Organizer"} +{"id": "GGOEGALJ149314","name": "GGOEGALJ149314","title": "Google Kirkland Campus Ladies Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1493.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Kirkland+Campus+Ladies+Tee"} +{"id": "GGOEGACH161516","name": "GGOEGACH161516","title": "Google Land Sea French Terry Sweatshirt","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGCOGXXX1609.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Land+and+Sea+French+Terry+Sweatshirt+LS"} +{"id": "GGOEGACH161517","name": "GGOEGACH161517","title": "Google Land Sea French Terry Sweatshirt","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGCOGXXX1609.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Land+and+Sea+French+Terry+Sweatshirt+LS"} +{"id": "GGCOGAED156912","name": "GGCOGAED156912","title": "Google Land Sea Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGCOGXXX1569.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Land+and+Sea+Unisex+Tee"} +{"id": "GGOEGOAR090099","name": "GGOEGOAR090099","title": "Google Light Pen Red","brands": ["Google"],"categories": ["Office"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "3"},"colorInfo": {"colorFamilies": ["Red"],"colors": ["Red","Flame red","Dark red"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGOAR090099.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Metal","Recycled Plastic"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Office/Google+Light+Up+Pen+Red"} +{"id": "GGOEGCBA168999","name": "GGOEGCBA168999","title": "Google Patch","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "3.5"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGCBA168999.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Lifestyle/Google+Patch"} +{"id": "GGOEGOAC123799","name": "GGOEGOAC123799","title": "Google Pen Bright Blue","brands": ["Google"],"categories": ["Office"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "1.75"},"colorInfo": {"colorFamilies": ["Blue"],"colors": ["Light blue","Blue","Dark blue"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGOAC123799.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Metal","Recycled Plastic"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Office/Google+Pen+Bright+Blue"} +{"id": "GGOEGAEL146912","name": "GGOEGAEL146912","title": "Google SF Campus Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1469.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+SF+Campus+Unisex+Tee"} +{"id": "GGOEGAEJ118215","name": "GGOEGAEJ118215","title": "Google Summer19 Crew Grey","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "32"},"colorInfo": {"colorFamilies": ["Gray"],"colors": ["Light gray","Silver"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1182.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2020.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Google+Summer19+Crew+Grey"} +{"id": "GGOEGAEJ153515","name": "GGOEGAEJ153515","title": "Google Sunnyvale Campus Zip Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "38"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1535.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Campus+Collection/Google+Sunnyvale+Campus+Zip+Hoodie"} +{"id": "GGCOGAEJ153717","name": "GGCOGAEJ153717","title": "Google TYCTWD Gray Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1537.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/TYCTWD/Google+TYCTWD+Charcoal+Tee"} +{"id": "GGCOGAXT154229","name": "GGCOGAXT154229","title": "Google TYCTWD Yellow Toddler Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "23"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1542.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/TYCTWD/Google+TYCTWD+Yellow+Toddler+Tee"} +{"id": "GGOEGAEC090718","name": "GGOEGAEC090718","title": "Google Tee Blue","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["Blue"],"colors": ["Light blue","Blue","Dark blue"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX0907.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Tee+Blue"} +{"id": "GGOEGAXQ134629","name": "GGOEGAXQ134629","title": "Google Toddler Tee White V2","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "24"},"colorInfo": {"colorFamilies": ["White"],"colors": ["White"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1346.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Toddler+Tee+White"} +{"id": "GGOEGAED175017","name": "GGOEGAED175017","title": "Google Tonal Brick Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "28"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Tonal+Brick+Tee"} +{"id": "GGOEGAEC173818","name": "GGOEGAEC173818","title": "Google Tonal Shirt Marine Blue","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "27"},"colorInfo": {"colorFamilies": ["Blue"],"colors": ["Light blue","Blue","Dark blue"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Tonal+Shirt+Marine+Blue"} +{"id": "GGOEGAEB125312","name": "GGOEGAEB125312","title": "Google Unisex Pride Eco-Tee Black","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Onyx","Ebony","Outer Space"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1253.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino","Membrane"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Unisex+Pride+Eco-Tee+Black"} +{"id": "GGOEGAEC164612","name": "GGOEGAEC164612","title": "Google Vintage Navy Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "27"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy","Dark blue"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1646.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Vintage+Navy+Tee"} +{"id": "GGOEGABB099199","name": "GGOEGABB099199","title": "Google Wallet Stand Black","brands": ["Google"],"categories": ["Office"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "3"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Onyx","Ebony","Outer Space"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGABB099199.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Accessories/Google+Wallet+Stand+Black"} +{"id": "GGOEGAPJ108216","name": "GGOEGAPJ108216","title": "Google Women's Discovery Lt. Rain Shell","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "38"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1082.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Womens+Discovery"} +{"id": "GGOEGALB109913","name": "GGOEGALB109913","title": "Google Women's Tee F/C Black","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Onyx","Ebony","Outer Space"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1099.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Womens+Tee+FC+Black"} +{"id": "GGOEGAYB116714","name": "GGOEGAYB116714","title": "Google Youth F/C Pullover Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1167.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual","Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Youth+FC+Pullover+Hoodie"} +{"id": "GGOEYCBR138999","name": "GGOEYCBR138999","title": "YouTube Iconic Play Pin","brands": ["YouTube"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "3"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEYCBR138999.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Accessories/YouTube+Iconic+Play+Pin"} +{"id": "GGCOGADC100814","name": "GGCOGADC100814","title": "#IamRemarkable Hoodie","brands": ["#IamRemarkable"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "32"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGCOGXXX1008.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/IamRemarkable+Hoodie"} +{"id": "GGOEAFKQ130599","name": "GGOEAFKQ130599","title": "Android Iconic 4in Decal","brands": ["Android"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "1.5"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEAFKQ130599.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Accessories/Android+Iconic+4in+Decal"} +{"id": "GGOEAAEL130815","name": "GGOEAAEL130815","title": "Android Iconic Crew","brands": ["Android"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "32"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEAXXX1308.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Android+Iconic+Crew"} +{"id": "GGOEGCOA173158","name": "GGOEGCOA173158","title": "Google Bike Paper Clip Set","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "3.5"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Bike+Paper+Clip+Set"} +{"id": "GGOEGALJ141117","name": "GGOEGALJ141117","title": "Google Chicago Campus Ladies Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1411.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Chicago+Campus+Ladies+Tee"} +{"id": "GGOEGCBA169599","name": "GGOEGCBA169599","title": "Google Chicago Campus Patch Set","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "16"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGCBA169599.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Campus+Collection/Google+Chicago+Campus+Patch+Set"} +{"id": "GGOECAEB165513","name": "GGOECAEB165513","title": "Google Cloud Tri-Blend Crew Tee","brands": ["Google Cloud"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOECXXX1655.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Cloud+Unisex+Tri-Blend+Crew+Tee"} +{"id": "GGOEGDNQ138099","name": "GGOEGDNQ138099","title": "Google Cork Base Tumbler","categories": ["Drinkware"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "28"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGDNQ138099.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Drinkware/Google+Cork+Base+Tumbler"} +{"id": "GGOEGAEL091315","name": "GGOEGAEL091315","title": "Google Crewneck Sweatshirt Navy","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy","Dark blue"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX0913.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Crew+Sweater+Navy"} +{"id": "GGPRGAEL101415","name": "GGPRGAEL101415","title": "Google Crewneck Sweatshirt Navy","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "32"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy","Dark blue"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20210405858/assets/items/images/GGPRGXXX1014.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Google+Crewneck+Sweatshirt+Navy"} +{"id": "GGOEGAWH144552","name": "GGOEGAWH144552","title": "Google Infant Hero Tee Olive","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1445.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Infant+Hero+Tee+Olive"} +{"id": "GGOEGAEH146018","name": "GGOEGAEH146018","title": "Google LA Campus Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1460.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+LA+Campus+Unisex+Tee"} +{"id": "GGOEGAEJ146218","name": "GGOEGAEJ146218","title": "Google LA Campus Zip Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "38"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1462.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+LA+Campus+Zip+Hoodie"} +{"id": "GGOEGADB138314","name": "GGOEGADB138314","title": "Google Men's Puff Jacket Black","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "44"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Onyx","Ebony","Outer Space"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1383.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Mens+Puff+Jacket+Black"} +{"id": "GGOEGDHH177299","name": "GGOEGDHH177299","title": "Google Olive Tundra Bottle","brands": ["Google"],"categories": ["Drinkware"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "31"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Olive+Tundra+Bottle"} +{"id": "GGOEGAEC153118","name": "GGOEGAEC153118","title": "Google Sunnyvale Campus Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1531.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Campus+Collection/Google+Sunnyvale+Campus+Unisex+Tee"} +{"id": "GGOEGAEC090713","name": "GGOEGAEC090713","title": "Google Tee Blue","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["Blue"],"colors": ["Light blue","Blue","Dark blue"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX0907.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Tee+Blue"} +{"id": "GGOEGAXC171928","name": "GGOEGAXC171928","title": "Google Tricyle Toddler Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "23"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1719.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Tricyle+Toddler+Tee"} +{"id": "GGOEGAEJ173613","name": "GGOEGAEJ173613","title": "Google Ultralight Gray Sweatshirt","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"colorInfo": {"colorFamilies": ["Gray"],"colors": ["Light gray","Silver"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Ultralight+Gray+Sweatshirt"} +{"id": "GGOEGAEJ173615","name": "GGOEGAEJ173615","title": "Google Ultralight Gray Sweatshirt","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"colorInfo": {"colorFamilies": ["Gray"],"colors": ["Stone gray","Cool gray"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Ultralight+Gray+Sweatshirt"} +{"id": "GGOEGAEQ120116","name": "GGOEGAEQ120116","title": "Google Unisex 3/4 Raglan Red","categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"colorInfo": {"colorFamilies": ["Red"],"colors": ["Red","Flame red","Dark red"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1201.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Unisex+3+4+Raglan+Red"} +{"id": "GGOEGADB176812","name": "GGOEGADB176812","title": "Google Unisex Puffer Jacket","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "36"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Unisex+Puffer+Jacket"} +{"id": "GGOEGAEC164617","name": "GGOEGAEC164617","title": "Google Vintage Navy Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "27"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy","Dark blue"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1646.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Vintage+Navy+Tee"} +{"id": "GGOEACBA116699","name": "GGOEACBA116699","title": "Noogler Android Figure 2019","brands": ["Android"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "16"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEACBA116699.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Accessories/Noogler+Android+Figure+2019"} +{"id": "GGOEYAXB089629","name": "GGOEYAXB089629","title": "YouTube Kids Tee Black","brands": ["YouTube"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "20"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Onyx","Ebony","Outer Space","Jet"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEYXXX0896.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Kids/Youtube+Kids+Tee+Black"} +{"id": "GGOEYALQ091917","name": "GGOEYALQ091917","title": "YouTube Women's Favorite Tee White","brands": ["YouTube"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["White"],"colors": ["White"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX0919.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Youtube+Favorite+Tee+White"} +{"id": "GGOEAAYL130315","name": "GGOEAAYL130315","title": "Android Pocket Youth Tee Navy","brands": ["Android"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy","Dark blue"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEAXXX1303.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Android+Pocket+Youth+Tee+Navy"} +{"id": "GGOEGPJC019099","name": "GGOEGPJC019099","title": "Google 7-inch Dog Flying Disc Blue","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "1.5"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGPJC019099.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Lifestyle/Google-Frisbee"} +{"id": "GGOEGAED142612","name": "GGOEGAED142612","title": "Google Austin Campus Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1426.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Austin+Campus+Unisex+Tee"} +{"id": "GGOEGAEJ144113","name": "GGOEGAEJ144113","title": "Google Boulder Campus Zip Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "38"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1441.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Boulder+Campus+Zip+Hoodie"} +{"id": "GGOEGAEJ133712","name": "GGOEGAEJ133712","title": "Google Cambridge Campus Zip Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "38"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1337.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Cambridge+Campus+Zip+Hoodie"} +{"id": "GGOEGCBA096099","name": "GGOEGCBA096099","title": "Google Campus Bike","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGCBA096099.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Accessories/Google+Campus+Bike"} +{"id": "GGOECAEB165413","name": "GGOECAEB165413","title": "Google Cloud Carhartt Crew Sweatshirt","brands": ["Google Cloud"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOECXXX1654.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Cloud+Unisex+Carhartt+Crew+Sweatshirt"} +{"id": "GGOEGAEL091312","name": "GGOEGAEL091312","title": "Google Crewneck Sweatshirt Navy","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy","Dark blue"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX0913.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Crew+Sweater+Navy"} +{"id": "GGOEGAEL091318","name": "GGOEGAEL091318","title": "Google Crewneck Sweatshirt Navy","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy","Dark blue"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX0913.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Crew+Sweater+Navy"} +{"id": "GGOEGBMH177899","name": "GGOEGBMH177899","title": "Google ecofriendly Green Duffel","brands": ["Google"],"categories": ["Bags"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "31"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive","Grass green"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+ecofriendly+Green+Duffel"} +{"id": "GGOEGBMR177799","name": "GGOEGBMR177799","title": "Google ecofriendly Red Duffel","brands": ["Google"],"categories": ["Bags"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "31"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+ecofriendly+Red+Duffel"} +{"id": "GGOEGAEB103815","name": "GGOEGAEB103815","title": "Google F/C Longsleeve Charcoal","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1038.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+FC+Longsleeve+Charcoal"} +{"id": "GGPRGBRC103299","name": "GGPRGBRC103299","title": "Google Incognito Dopp Kit V2","brands": ["Google"],"categories": ["Bags"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "38"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20210405858/assets/items/images/GGPRGBRC103299.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Google+Incognito+Dopp+Kit+V2"} +{"id": "GGPRGCBA100399","name": "GGPRGCBA100399","title": "Google Journal Set","brands": ["Google"],"categories": ["Office"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "10.75"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20210405858/assets/items/images/GGPRGCBA100399.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Google+Journal+Set"} +{"id": "GGOEGAYC118315","name": "GGOEGAYC118315","title": "Google Kids Playful Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1183.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Google+Kids+Playful+Tee"} +{"id": "GGOEGDHJ145999","name": "GGOEGDHJ145999","title": "Google LA Campus Bottle","brands": ["Google"],"categories": ["Drinkware"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "20"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGDHJ145999.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Drinkware/Google+LA+Campus+Bottle"} +{"id": "GGOEMAEB164115","name": "GGOEMAEB164115","title": "Google F/C Charcoal","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "21"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEMXXX1641.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Maps+Pin+Tee"} +{"id": "GGOEGAEB165317","name": "GGOEGAEB165317","title": "Google Marine Layer Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1653.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Marine+Layer+Tee"} +{"id": "GGOEGADH138114","name": "GGOEGADH138114","title": "Google Men's Softshell Moss","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "39"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1381.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Mens+Softshell+Moss"} +{"id": "GGOEGAEC119613","name": "GGOEGAEC119613","title": "Google Mountain View Tee Blue","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"colorInfo": {"colorFamilies": ["Blue"],"colors": ["Light blue","Blue","Dark blue"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1196.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Mountain+View+Tee+Blue"} +{"id": "GGOEGAEC165212","name": "GGOEGAEC165212","title": "Google Navy French Terry Zip Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy","Dark blue"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1652.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Navy+French+Terry+Zip+Hoodie"} +{"id": "GGOEGCBA169999","name": "GGOEGCBA169999","title": "Google New York Campus Patch Set","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "16"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGCBA169999.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Campus+Collection/Google+New+York+Campus+Patch+Set"} +{"id": "GGOEGBJD148499","name": "GGOEGBJD148499","title": "Google PNW Campus Tote","brands": ["Google"],"categories": ["Bags"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "11"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGBJD148499.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Bags/Google+PNW+Campus+Tote"} +{"id": "GGOEGAAR134513","name": "GGOEGAAR134513","title": "Google Red Speckled Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1345.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Red+Speckled+Tee"} +{"id": "GGOEGAEH148715","name": "GGOEGAEH148715","title": "Google Seattle Campus Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1487.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Seattle+Campus+Unisex+Tee"} +{"id": "GGOEGADC134712","name": "GGOEGADC134712","title": "Google Sherpa Zip Hoodie Navy","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "39"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy","Dark blue"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1347.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Sherpa+Zip+Hoodie+Navy"} +{"id": "GGOEGAEC134910","name": "GGOEGAEC134910","title": "Google Speckled Beanie Navy","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "20"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy","Dark blue"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGAEC134910.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Speckled+Beanie+Navy"} +{"id": "GGOEGOCB178199","name": "GGOEGOCB178199","title": "Google Stitched Journal Set","brands": ["Google"],"categories": ["Office"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Stitched+Journal+Set"} +{"id": "GGOEGAXB113351","name": "GGOEGAXB113351","title": "Google Toddler FC Tee Charcoal","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1133.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Toddler+FC+Tee+Charcoal"} +{"id": "GGOEGAED175014","name": "GGOEGAED175014","title": "Google Tonal Brick Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "28"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Tonal+Brick+Tee"} +{"id": "GGOEGADB176613","name": "GGOEGADB176613","title": "Google Unisex Puffer Vest","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "34"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Unisex+Puffer+Vest"} +{"id": "GGOEGAPJ178414","name": "GGOEGAPJ178414","title": "Google Women's Essential Jacket","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "38"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1784.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Womens+Essential+Jacket"} +{"id": "GGOEGAPB096315","name": "GGOEGAPB096315","title": "Google Womens Microfleece Jacket Black","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Onyx","Ebony"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX0963.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Womens+Microfleece+Jacket+Black"} +{"id": "GGOEYAXB089655","name": "GGOEYAXB089655","title": "YouTube Kids Tee Black","brands": ["YouTube"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "20"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Outer Space","Jet"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEYXXX0896.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Kids/Youtube+Kids+Tee+Black"} +{"id": "GGOEYAEJ092115","name": "GGOEYAEJ092115","title": "Youtube 3 lines Tee Grey","brands": ["YouTube"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["Gray"],"colors": ["Light gray","Silver"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX0921.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Youtube+3+lines+tee+grey"} +{"id": "GGCOGADC100813","name": "GGCOGADC100813","title": "#IamRemarkable Hoodie","brands": ["#IamRemarkable"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "32"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGCOGXXX1008.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/IamRemarkable+Hoodie"} +{"id": "GGCOGAEC100616","name": "GGCOGAEC100616","title": "#IamRemarkable T-Shirt","brands": ["#IamRemarkable"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "12"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGCOGXXX1006.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/IamRemarkable+Unisex+T-Shirt"} +{"id": "GGOEAAEL130813","name": "GGOEAAEL130813","title": "Android Iconic Crew","brands": ["Android"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "32"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEAXXX1308.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Android+Iconic+Crew"} +{"id": "GGOEAAEL130818","name": "GGOEAAEL130818","title": "Android Iconic Crew","brands": ["Android"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "32"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEAXXX1308.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Android+Iconic+Crew"} +{"id": "GGOEAAWL130145","name": "GGOEAAWL130145","title": "Android Pocket Onesie Navy","brands": ["Android"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy","Dark blue"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEAXXX1301.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Android+Pocket+Onesie+Navy"} +{"id": "GGPRAAEH107214","name": "GGPRAAEH107214","title": "Android Pocket Tee Green","brands": ["Android"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "29"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive","Grass green"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEAXXX1296.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Android+Pocket+Tee+Green"} +{"id": "GGOEGAEC171816","name": "GGOEGAEC171816","title": "Google Bike Eco Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1718.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Bike+Eco+Tee"} +{"id": "GGOECAEB163513","name": "GGOECAEB163513","title": "Google Black Cloud Polo","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "36"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Ebony","Outer Space","Jet"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOECXXX1635.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Black+Cloud+Polo"} +{"id": "GGOEGALC133617","name": "GGOEGALC133617","title": "Google Cambridge Campus Ladies Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1336.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Cambridge+Campus+Ladies+Tee"} +{"id": "GGOEGAEC176217","name": "GGOEGAEC176217","title": "Google Camp Fleece Snap Pullover","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "32"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Camp+Fleece+Snap+Pullover"} +{"id": "GGPRGBJC103999","name": "GGPRGBJC103999","title": "Google Campus Bike Tote Navy","brands": ["Google"],"categories": ["Bags"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "3.4"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy","Dark blue"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20210405858/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Google+Campus+Bike+Tote+Navy"} +{"id": "GGOEGAEJ168513","name": "GGOEGAEJ168513","title": "Google Campus Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1685.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Campus+Unisex+Tee"} +{"id": "GGOEGDWH175999","name": "GGOEGDWH175999","title": "Google Ceramic Glazed Mug","brands": ["Google"],"categories": ["Drinkware"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "12"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Ceramic+Glazed+Mug"} +{"id": "GGOEGAEJ163317","name": "GGOEGAEJ163317","title": "Google Charcoal Unisex Badge Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "21"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1633.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Charcoal+Unisex+Badge+Tee"} +{"id": "GGPRGCBA100499","name": "GGPRGCBA100499","title": "Google Confetti Task Pad","brands": ["Google"],"categories": ["Office"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "3.75"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20210405858/assets/items/images/GGPRGCBA100499.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Google+Confetti+Combo"} +{"id": "GGPRGOCA102299","name": "GGPRGOCA102299","title": "Google Confetti Slim Task Pad","brands": ["Google"],"categories": ["Office"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "3"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20210405858/assets/items/images/GGPRGOCA102299.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Google+Confetti+Slim+Task+Pad"} +{"id": "GGPRGOCD102099","name": "GGPRGOCD102099","title": "Google Cork Journal","brands": ["Google"],"categories": ["Office"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "3"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20210405858/assets/items/images/GGPRGOCD102099.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Google+Cork+Journal"} +{"id": "GGOEGAEJ096412","name": "GGOEGAEJ096412","title": "Google Crewneck Sweatshirt Grey","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"colorInfo": {"colorFamilies": ["Gray"],"colors": ["Light gray","Silver"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX0964.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Crew+Grey"} +{"id": "GGOEGAEL091316","name": "GGOEGAEL091316","title": "Google Crewneck Sweatshirt Navy","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy","Dark blue"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX0913.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Crew+Sweater+Navy"} +{"id": "GGOEGHGA174599","name": "GGOEGHGA174599","title": "Google Gradient Green Sunglasses","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "3"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive","Grass green"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Gradient+Green+Sunglasses"} +{"id": "GGOEGAER149216","name": "GGOEGAER149216","title": "Google Kirkland Campus Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1492.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Kirkland+Campus+Unisex+Tee"} +{"id": "GGOEGCBA139899","name": "GGOEGCBA139899","title": "Google Large Pet Leash (Blue/Green)","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"colorInfo": {"colorFamilies": ["Blue"],"colors": ["Light blue","Blue","Dark blue"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGCBA139899.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Accessories/Google+Large+Pet+Leash+Blue+Green"} +{"id": "GGOEGAEB165316","name": "GGOEGAEB165316","title": "Google Marine Layer Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1653.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Cotton"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Marine+Layer+Tee"} +{"id": "GGOEGBJA127699","name": "GGOEGBJA127699","title": "Google Mural Tote","brands": ["Google"],"categories": ["Bags"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "18"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGBJA127699.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Bags/Google+Mural+Tote"} +{"id": "GGOEGAEC165217","name": "GGOEGAEC165217","title": "Google Navy French Terry Zip Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1652.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Navy+French+Terry+Zip+Hoodie"} +{"id": "GGOEGOAQ101299","name": "GGOEGOAQ101299","title": "Google Pen White","brands": ["Google"],"categories": ["Office"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "1.75"},"colorInfo": {"colorFamilies": ["White"],"colors": ["White"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGOAQ101299.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Metal","Recycled Plastic"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Office/Google+Pen+White"} +{"id": "GGOEGADC135016","name": "GGOEGADC135016","title": "Google Raincoat Navy","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "44"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1350.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Raincoat+Navy"} +{"id": "GGCOAAPR155410","name": "GGCOAAPR155410","title": "Google TYCTWD Red Cap","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "13"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGAPR155410.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/TYCTWD/Google+TYCTWD+Red+Cap"} +{"id": "GGOEGAEH090616","name": "GGOEGAEH090616","title": "Google Tee Green","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive","Grass green"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX0906.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Greenesign/Apparel/Google+Tee+Green"} +{"id": "GGOEGAXB113330","name": "GGOEGAXB113330","title": "Google Toddler FC Tee Charcoal","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1133.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Toddler+FC+Tee+Charcoal"} +{"id": "GGOEGAXB113617","name": "GGOEGAXB113617","title": "Google Toddler FC Zip Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1136.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Toddler+FC+Zip+Hoodie"} +{"id": "GGOEGAEC164718","name": "GGOEGAEC164718","title": "Google Tonal Blue Eco Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"colorInfo": {"colorFamilies": ["Blue"],"colors": ["Light blue","Blue","Dark blue"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1647.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Tonal+Blue+Eco+Tee"} +{"id": "GGOEGAED175018","name": "GGOEGAED175018","title": "Google Tonal Brick Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "28"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Tonal+Brick+Tee"} +{"id": "GGPRGALB100815","name": "GGPRGALB100815","title": "Google Women's Eco Tee Black","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Onyx","Ebony","Jet"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20210405858/assets/items/images/GGPRGXXX1008.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Google+Womens+Eco+Tee+Black"} +{"id": "GGOEGATJ137214","name": "GGOEGATJ137214","title": "Google Women's Tech Fleece Vest Charcoal","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "39"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1372.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Womens+Tech+Fleece+Vest+Charcoal"} +{"id": "GGPRACBA107014","name": "GGPRACBA107014","title": "I \u003c3 Android Kit","brands": ["Android"],"categories": ["Kit"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "44.75"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20210405858/assets/items/images/GGPRAXXX1070.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/I+Love+Android+Kit"} +{"id": "GGOEGAWH126846","name": "GGOEGAWH126846","title": "Stan and Friends 2019 Onesie","brands": ["Stan and Friends"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive","Grass green"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1268.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Stan+and+Friends+Onesie+Green"} +{"id": "GGOEGAYH126913","name": "GGOEGAYH126913","title": "Stan and Friends 2019 Youth Tee","brands": ["Stan and Friends"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive","Grass green"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1269.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Stan+and+Friends+Youth+Tee+Green"} +{"id": "GGCOGADC100816","name": "GGCOGADC100816","title": "#IamRemarkable Hoodie","brands": ["#IamRemarkable"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "32"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGCOGXXX1008.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/IamRemarkable+Hoodie"} +{"id": "GGOEAAEC172017","name": "GGOEAAEC172017","title": "Android Embroidered Crewneck Sweater","brands": ["Android"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Android+Embroidered+Crewneck+Sweater"} +{"id": "GGOEAHPL130910","name": "GGOEAHPL130910","title": "Android Iconic Hat Green","brands": ["Android"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "16"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive","Grass green"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEAHPL130910.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Android+Iconic+Hat+Green"} +{"id": "GGOEAAEH129616","name": "GGOEAAEH129616","title": "Android Pocket Tee Green","brands": ["Android"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "29"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive","Grass green","Light green"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEAXXX1296.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Android+Pocket+Tee+Green"} +{"id": "GGOEAAYL130313","name": "GGOEAAYL130313","title": "Android Pocket Youth Tee Navy","brands": ["Android"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEAXXX1303.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Android+Pocket+Youth+Tee+Navy"} +{"id": "GGOEGCBA169899","name": "GGOEGCBA169899","title": "Google Austin Campus Patch Set","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "16"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGCBA169899.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Campus+Collection/Google+Austin+Campus+Patch+Set"} +{"id": "GGOEGAXN127229","name": "GGOEGAXN127229","title": "Google Beekeepers 2019 Toddler Tee, Pink","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1272.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/ApparelGoogle+Beekeepers+Toddler+Tee+Pink"} +{"id": "GGOEGDWJ141799","name": "GGOEGDWJ141799","title": "Google Camp Mug Gray","brands": ["Google"],"categories": ["Drinkware"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "13"},"colorInfo": {"colorFamilies": ["Gray"],"colors": ["Light gray","Cool gray"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGDWJ141799.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Lifestyle/Google+Camp+Mug+Gray"} +{"id": "GGPRGCBA101299","name": "GGPRGCBA101299","title": "Google Cork Set","brands": ["Google"],"categories": ["Office"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "39"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20210405858/assets/items/images/GGPRGCBA101299.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Google+Cork+Set"} +{"id": "GGOEGAED168115","name": "GGOEGAED168115","title": "Google Earth Day Eco Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1681.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Earth+Day+Eco+Tee"} +{"id": "GGOEGAEJ103917","name": "GGOEGAEJ103917","title": "Google F/C Longsleeve Ash","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1039.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+FC+Longsleeve+Ash"} +{"id": "GGOEGFSR022099","name": "GGOEGFSR022099","title": "Google Kick Ball","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "2"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGFSR022099.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Lifestyle/Fun/Google+Kick+Ball.axd"} +{"id": "GGCOGCBA164499","name": "GGCOGCBA164499","title": "Google Knit Blanket","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "0"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGCOGCBA164499.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Lifestyle/Google+Knit+Blanket"} +{"id": "GGOEGAEJ146213","name": "GGOEGAEJ146213","title": "Google LA Campus Zip Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "38"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1462.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+LA+Campus+Zip+Hoodie"} +{"id": "GGOEGACH161518","name": "GGOEGACH161518","title": "Google Land Sea French Terry Sweatshirt","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGCOGXXX1609.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Land+and+Sea+French+Terry+Sweatshirt+LS"} +{"id": "GGCOGCBA161199","name": "GGCOGCBA161199","title": "Google Land Sea Tech Taco","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "3"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGCOGCBA161199.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Land+and+Sea+Tech+Taco"} +{"id": "GGOEGAED161615","name": "GGOEGAED161615","title": "Google Land Sea Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGCOGXXX1569.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Land+and+Sea+Unisex+Tee+LS"} +{"id": "GGOEMAEB164113","name": "GGOEMAEB164113","title": "Google Maps Pin Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "21"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEMXXX1641.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Maps+Pin+Tee"} +{"id": "GGOEGADH138118","name": "GGOEGADH138118","title": "Google Men's Softshell Moss","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "39"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1381.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Mens+Softshell+Moss"} +{"id": "GGOEGDHJ147999","name": "GGOEGDHJ147999","title": "Google PNW Campus Bottle","brands": ["Google"],"categories": ["Drinkware"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "20"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGDHJ147999.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Drinkware/Google+PNW+Campus+Bottle"} +{"id": "GGOEGOAJ101399","name": "GGOEGOAJ101399","title": "Google Pen Grey","brands": ["Google"],"categories": ["Office"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "1.75"},"colorInfo": {"colorFamilies": ["Gray"],"colors": ["Light gray","Silver"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGOAJ101399.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Metal","Recycled Plastic"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Office/Google+pen+grey"} +{"id": "GGPRGADC107915","name": "GGPRGADC107915","title": "Google Raincoat Navy","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "44"},"colorInfo": {"colorFamilies": ["Navy"],"colors": ["Navy"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1350.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Google+Raincoat+Navy"} +{"id": "GGOEGAAH136915","name": "GGOEGAAH136915","title": "Google Split Seam Tee Olive","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "26"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1369.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Split+Seam+Tee+Olive"} +{"id": "GGCOGAEC153813","name": "GGCOGAEC153813","title": "Google TYCTWD Blue Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"colorInfo": {"colorFamilies": ["Blue"],"colors": ["Light blue","Blue","Dark blue"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1538.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/TYCTWD/Google+TYCTWD+Blue+Tee"} +{"id": "GGOEGAEB110018","name": "GGOEGAEB110018","title": "Google Tee F/C Black","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Onyx","Ebony"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1100.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Tee+FC+Black"} +{"id": "GGOEGAAB118914","name": "GGOEGAAB118914","title": "Google Unisex Eco Tee Black","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Onyx","Jet"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1189.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Unisex+Eco+Tee+Black"} +{"id": "GGOEGAAB118916","name": "GGOEGAAB118916","title": "Google Unisex Eco Tee Black","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Outer Space","Jet"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1189.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Unisex+Eco+Tee+Black"} +{"id": "GGOEGAEJ178314","name": "GGOEGAEJ178314","title": "Google Unisex Essential Jacket","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "38"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1783.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Unisex+Essential+Jacket"} +{"id": "GGOEGAPJ178416","name": "GGOEGAPJ178416","title": "Google Women's Essential Jacket","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "38"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1784.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Womens+Essential+Jacket"} +{"id": "GGOEGCBD165799","name": "GGOEGCBD165799","title": "Google Wooden Yo-Yo","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "3"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGCBD165799.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Wooden+Yo+Yo"} +{"id": "GGOEGAYJ136014","name": "GGOEGAYJ136014","title": "Google Youth Hero Tee Grey","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "24"},"colorInfo": {"colorFamilies": ["Gray"],"colors": ["Light gray","Silver"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1360.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Youth+Hero+Tee+Grey"} +{"id": "GGOEYAEB120713","name": "GGOEYAEB120713","title": "YouTube Standards Zip Hoodie Black","brands": ["YouTube"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Onyx","Jet"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEYXXX1207.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/YouTube+Standards+Zip+Hoodie+Black"} +{"id": "GGOEYAEB120715","name": "GGOEYAEB120715","title": "YouTube Standards Zip Hoodie Black","brands": ["YouTube"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"colorInfo": {"colorFamilies": ["Black"],"colors": ["Onyx","Outer Space","Jet"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEYXXX1207.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/YouTube+Standards+Zip+Hoodie+Black"} +{"id": "GGOEYALQ091914","name": "GGOEYALQ091914","title": "YouTube Women's Favorite Tee White","brands": ["YouTube"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"colorInfo": {"colorFamilies": ["White"],"colors": ["White"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX0919.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Youtube+Favorite+Tee+White"} +{"id": "GGOEGCKA151899","name": "GGOEGCKA151899","title": "Google 4in Decal","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "2"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGCKA151899.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Stationery/Google+4in+Decal"} +{"id": "GGOEGAEQ162514","name": "GGOEGAEQ162514","title": "Google 5k Run 2020 Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "22"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1625.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/5k+run/Google+5K+Run+2020+Unisex+Tee"} +{"id": "GGOEGEBK094499","name": "GGOEGEBK094499","title": "Google Bot Natural","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "10"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGEBK094499.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Accessories/Google+Bot"} +{"id": "GGPRGCBA106399","name": "GGPRGCBA106399","title": "Google Campus Bike","brands": ["Google"],"categories": ["Accessories"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGCBA096099.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Google+Campus+Bike"} +{"id": "GGOEGAER141015","name": "GGOEGAER141015","title": "Google Chicago Campus Unisex Tee","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "25"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1410.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Chicago+Campus+Unisex+Tee"} +{"id": "GGOEGAEC141218","name": "GGOEGAEC141218","title": "Google Chicago Campus Zip Hoodie","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "38"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1412.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Chicago+Campus+Zip+Hoodie"} +{"id": "GGOECAEB165414","name": "GGOECAEB165414","title": "Google Cloud Carhartt Crew Sweatshirt","brands": ["Google Cloud"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "30"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOECXXX1654.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Cloud+Unisex+Carhartt+Crew+Sweatshirt"} +{"id": "GGOEGAEJ178516","name": "GGOEGAEJ178516","title": "Google Cloud Packable Lightweight Jacket","brands": ["Google Cloud"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "32"},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/noimage.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Google+Cloud+Packable+Lightweight+Jacket"} +{"id": "GGOEGADH134212","name": "GGOEGADH134212","title": "Google Crewneck Sweatshirt Green","brands": ["Google"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "35"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive","Grass green","Light green"]},"availability": "OUT_OF_STOCK","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEGXXX1342.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2021.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Merino"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Casual"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Google+Crewneck+Sweatshirt+Green"} diff --git a/retail/interactive-tutorials/src/main/resources/products_some_invalid.json b/retail/interactive-tutorials/src/main/resources/products_some_invalid.json new file mode 100644 index 00000000000..f46dc76191c --- /dev/null +++ b/retail/interactive-tutorials/src/main/resources/products_some_invalid.json @@ -0,0 +1,3 @@ +{"id": "GGCOGOAC101259","name": "GGCOGOAC101259","title": "#IamRemarkable Pen","brands": ["#IamRemarkable"],"categories": ["Office"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "16"},"colorInfo": {"colorFamilies": ["Blue"],"colors": ["Light blue","Blue","Dark blue"]},"availability": "INVALID_VALUE","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGCOGOAC101259.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Metal","Recycled Plastic"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Office/IamRemarkable+Pen"} +{"id": "GGPRAHPL107110","name": "GGPRAHPL107110","title": "Android Iconic Hat Green","brands": ["Android"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "16"},"colorInfo": {"colorFamilies": ["Green"],"colors": ["Olive","Grass green","Light green"]},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEAHPL130910.jpg"}],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","uri": "https://shop.googlemerchandisestore.com/Google+Prize+Portal/Android+Iconic+Hat+Green"} +{"id": "GGOEAAKQ137410","name": "GGOEAAKQ137410","title": "Android Iconic Sock","brands": ["Android"],"categories": ["Apparel"],"priceInfo": {"cost": "12.0","currencyCode": "USD","originalPrice": "45.0","priceEffectiveTime": "2020-08-01T12:00:00+00:00","priceExpireTime": "2120-08-01T12:00:00+00:00","price": "17"},"availability": "IN_STOCK","availableQuantity": 50,"availableTime": "2021-10-11T12:00:00+00:00","images": [{"height": "300","width": "400","uri": "https://shop.googlemerchandisestore.com/store/20160512512/assets/items/images/GGOEAAKQ137410.jpg"}],"sizes": ["XS","S","M","L","XL"],"retrievableFields": "name,title,brands,categories,priceInfo,colorInfo,availability,images,attributes.material,attributes.ecofriendly,attributes.style,attributes.collection,uri","attributes":[ {"key":"collection", "value": {"indexable": "true","numbers": [2022.0]}},{"key":"material", "value": {"indexable": "true","searchable": "true","text": ["Polyester","Membrane"]}},{"key":"ecofriendly", "value": {"indexable": "false","searchable": "false","text": ["Low-impact fabrics","recycled fabrics","recycled packaging","plastic-free packaging","ethically made"]}},{"key":"style", "value": {"indexable": "true","searchable": "true","text": ["Sport","Functional"]}}],"uri": "https://shop.googlemerchandisestore.com/Google+Redesign/Apparel/Android+Iconic+Sock"} \ No newline at end of file diff --git a/retail/interactive-tutorials/src/main/resources/user_events.json b/retail/interactive-tutorials/src/main/resources/user_events.json new file mode 100644 index 00000000000..56bd7c443ff --- /dev/null +++ b/retail/interactive-tutorials/src/main/resources/user_events.json @@ -0,0 +1,4 @@ +{"eventType":"home-page-view","visitorId":"bjbs_group1_visitor1","eventTime":"2021-12-12T10:27:42+00:00"} +{"eventType":"search","visitorId":"bjbs_group1_visitor1","eventTime":"2021-12-12T10:27:42+00:00","searchQuery":"RockerJeans teenagers blue jeans"} +{"eventType":"search","visitorId":"bjbs_group1_visitor1","eventTime":"2021-12-12T10:27:42+00:00","searchQuery":"SocksUnlimited teenagers black socks"} +{"eventType":"detail-page-view","visitorId":"bjbs_group1_visitor1","eventTime":"2021-12-12T10:27:42+00:00","productDetails":{"product":{"id":"GGCOGAEC100616"},"quantity":3}} \ No newline at end of file diff --git a/retail/interactive-tutorials/src/main/resources/user_events_some_invalid.json b/retail/interactive-tutorials/src/main/resources/user_events_some_invalid.json new file mode 100644 index 00000000000..c98b1699647 --- /dev/null +++ b/retail/interactive-tutorials/src/main/resources/user_events_some_invalid.json @@ -0,0 +1,4 @@ +{"eventType":"home-page-view","visitorId":"bjbs_group1_visitor1","eventTime":"2021-12-12T10:27:42+00:00"} +{"eventType":"invalid","visitorId":"bjbs_group1_visitor1","eventTime":"2021-12-12T10:27:42+00:00","searchQuery":"RockerJeans teenagers blue jeans"} +{"eventType":"search","visitorId":"bjbs_group1_visitor1","eventTime":"2021-12-12T10:27:42+00:00","searchQuery":"SocksUnlimited teenagers black socks"} +{"eventType":"detail-page-view","visitorId":"bjbs_group1_visitor1","eventTime":"2021-12-12T10:27:42+00:00","productDetails":{"product":{"id":"GGCOGAEC100616"},"quantity":3}} diff --git a/retail/interactive-tutorials/src/test/java/events/ImportUserEventsBigQueryTest.java b/retail/interactive-tutorials/src/test/java/events/ImportUserEventsBigQueryTest.java new file mode 100644 index 00000000000..a7f9a7a6716 --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/events/ImportUserEventsBigQueryTest.java @@ -0,0 +1,103 @@ +/* + * 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 events; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.api.gax.rpc.InvalidArgumentException; +import com.google.cloud.ServiceOptions; +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ImportUserEventsBigQueryTest { + + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + } + + @Test + public void testValidImportUserEventsBigQuery() throws IOException, InterruptedException { + String projectId = ServiceOptions.getDefaultProjectId(); + String defaultCatalog = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + String datasetId = "user_events"; + String tableId = "events"; + + ImportUserEventsBigQuery.importUserEventsFromBigQuery( + projectId, defaultCatalog, datasetId, tableId); + + String outputResult = bout.toString(); + + assertThat(outputResult).contains("Import user events from BigQuery source request"); + assertThat(outputResult).contains("table_id: \"events\""); + assertThat(outputResult).contains("Number of successfully imported events:"); + assertThat(outputResult).contains("Number of failures during the importing: 0"); + } + + @Test + public void testInvalidImportUserEventsBigQuery() throws IOException, InterruptedException { + String projectId = ServiceOptions.getDefaultProjectId(); + String defaultCatalog = + String.format("projects/%s/locations/global/catalogs/invalid_catalog_name", projectId); + String datasetId = "user_events"; + String tableId = "events_some_invalid"; + + ImportUserEventsBigQuery.importUserEventsFromBigQuery( + projectId, defaultCatalog, datasetId, tableId); + + String outputResult = bout.toString(); + + assertThat(outputResult).contains("table_id: \"events_some_invalid\""); + assertThat(outputResult).contains("Catalog name is not found."); + } + + @Test + public void testInvalidDefaultCatalogBigQuery() { + String projectId = ServiceOptions.getDefaultProjectId(); + String defaultCatalog = "invalid_catalog_name"; + String datasetId = "user_events"; + String tableId = "events"; + + assertThrows( + InvalidArgumentException.class, + () -> + ImportUserEventsBigQuery.importUserEventsFromBigQuery( + projectId, defaultCatalog, datasetId, tableId)); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/src/test/java/events/ImportUserEventsGcsTest.java b/retail/interactive-tutorials/src/test/java/events/ImportUserEventsGcsTest.java new file mode 100644 index 00000000000..1829aab0a58 --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/events/ImportUserEventsGcsTest.java @@ -0,0 +1,70 @@ +/* + * 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 events; + +import static com.google.common.truth.Truth.assertThat; +import static events.ImportUserEventsGcs.importUserEventsFromGcs; + +import com.google.cloud.ServiceOptions; +import events.setup.EventsCreateGcsBucket; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ImportUserEventsGcsTest { + + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, InterruptedException { + EventsCreateGcsBucket.main(); + + final String projectId = ServiceOptions.getDefaultProjectId(); + final String defaultCatalog = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + final String bucketName = EventsCreateGcsBucket.getBucketName(); + final String gcsEventsObject = "user_events.json"; + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + + importUserEventsFromGcs(defaultCatalog, bucketName, gcsEventsObject); + } + + @Test + public void testValidImportUserEventsGcs() { + String outputResult = bout.toString(); + + assertThat(outputResult).contains("Import user events from google cloud source request"); + assertThat(outputResult).contains("Number of successfully imported events:"); + assertThat(outputResult).contains("Number of failures during the importing: 0"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/src/test/java/events/ImportUserEventsInlineTest.java b/retail/interactive-tutorials/src/test/java/events/ImportUserEventsInlineTest.java new file mode 100644 index 00000000000..d7a213f72f1 --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/events/ImportUserEventsInlineTest.java @@ -0,0 +1,66 @@ +/* + * 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 events; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.ServiceOptions; +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ImportUserEventsInlineTest { + + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, ExecutionException, InterruptedException { + final String projectId = ServiceOptions.getDefaultProjectId(); + final String defaultCatalog = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + + ImportUserEventsInline.importUserEventsFromInlineSource(defaultCatalog); + } + + @Test + public void testImportUserEventsInline() { + String outputResult = bout.toString(); + + assertThat(outputResult).contains("Import user events from inline source request"); + assertThat(outputResult).contains("The operation was started"); + assertThat(outputResult).contains("Number of successfully imported events"); + assertThat(outputResult).contains("Number of failures during the importing"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/src/test/java/events/PurgeUserEventTest.java b/retail/interactive-tutorials/src/test/java/events/PurgeUserEventTest.java new file mode 100644 index 00000000000..6108662243a --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/events/PurgeUserEventTest.java @@ -0,0 +1,67 @@ +/* + * 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 events; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.ServiceOptions; +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class PurgeUserEventTest { + + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + final String projectId = ServiceOptions.getDefaultProjectId(); + final String defaultCatalog = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + final String visitorId = UUID.randomUUID().toString(); + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + + PurgeUserEvent.callPurgeUserEvents(defaultCatalog, visitorId); + } + + @Test + public void testPurgeUserEvent() { + String outputResult = bout.toString(); + + assertThat(outputResult).contains("The user event is written"); + assertThat(outputResult).contains("Purge user events request"); + assertThat(outputResult).contains("The purge operation was started"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/src/test/java/events/RejoinUserEventTest.java b/retail/interactive-tutorials/src/test/java/events/RejoinUserEventTest.java new file mode 100644 index 00000000000..2d2f6f34a77 --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/events/RejoinUserEventTest.java @@ -0,0 +1,67 @@ +/* + * 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 events; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.ServiceOptions; +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class RejoinUserEventTest { + + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + final String projectId = ServiceOptions.getDefaultProjectId(); + final String defaultCatalog = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + final String visitorId = UUID.randomUUID().toString(); + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + + RejoinUserEvent.callRejoinUserEvents(defaultCatalog, visitorId); + } + + @Test + public void testPurgeUserEvent() { + String outputResult = bout.toString(); + + assertThat(outputResult).contains("The user event is written"); + assertThat(outputResult).contains("Rejoin user events request"); + assertThat(outputResult).contains("The rejoin operation was started"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/src/test/java/events/WriteUserEventTest.java b/retail/interactive-tutorials/src/test/java/events/WriteUserEventTest.java new file mode 100644 index 00000000000..2b842e9fc2e --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/events/WriteUserEventTest.java @@ -0,0 +1,66 @@ +/* + * 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 events; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.ServiceOptions; +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class WriteUserEventTest { + + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + final String projectId = ServiceOptions.getDefaultProjectId(); + final String defaultCatalog = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + final String visitorId = UUID.randomUUID().toString(); + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + + WriteUserEvent.writeUserEvent(defaultCatalog, visitorId); + } + + @Test + public void testPurgeUserEvent() { + String outputResult = bout.toString(); + + assertThat(outputResult).contains("Write user event request"); + assertThat(outputResult).contains("Written user event"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/src/test/java/product/AddFulfillmentPlacesTest.java b/retail/interactive-tutorials/src/test/java/product/AddFulfillmentPlacesTest.java new file mode 100644 index 00000000000..38c7824004f --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/product/AddFulfillmentPlacesTest.java @@ -0,0 +1,76 @@ +/* + * 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 product; + +import static com.google.common.truth.Truth.assertThat; +import static product.AddFulfillmentPlaces.addFulfillmentPlaces; +import static setup.SetupCleanup.createProduct; +import static setup.SetupCleanup.deleteProduct; +import static setup.SetupCleanup.getProduct; + +import com.google.cloud.ServiceOptions; +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class AddFulfillmentPlacesTest { + + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + // TODO(developer): Replace these variables before running the sample. + final String projectId = ServiceOptions.getDefaultProjectId(); + final String generatedProductId = UUID.randomUUID().toString(); + final String productName = + String.format( + "projects/%s/locations/global/catalogs/default_catalog/branches/0/products/%s", + projectId, generatedProductId); + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + + createProduct(generatedProductId); + addFulfillmentPlaces(productName, "store2"); + getProduct(productName); + deleteProduct(productName); + } + + @Test + public void testAddFulfillment() { + String outputResult = bout.toString(); + + assertThat(outputResult).contains("Add fulfilment places"); + assertThat(outputResult).contains("Waiting for operation to finish..."); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/src/test/java/product/CreateProductTest.java b/retail/interactive-tutorials/src/test/java/product/CreateProductTest.java new file mode 100644 index 00000000000..46dc70ed914 --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/product/CreateProductTest.java @@ -0,0 +1,71 @@ +/* + * 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 product; + +import static com.google.common.truth.Truth.assertThat; +import static product.CreateProduct.createProduct; +import static setup.SetupCleanup.deleteProduct; + +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.Product; +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class CreateProductTest { + + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + final String projectId = ServiceOptions.getDefaultProjectId(); + final String branchName = + String.format( + "projects/%s/locations/global/catalogs/default_catalog/branches/0", projectId); + final String generatedProductId = UUID.randomUUID().toString(); + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + + Product createdProduct = createProduct(generatedProductId, branchName); + deleteProduct(createdProduct.getName()); + } + + @Test + public void testCreateProduct() { + String outputResult = bout.toString(); + + assertThat(outputResult).contains("Create product request"); + assertThat(outputResult).contains("Created product"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/src/test/java/product/CrudProductTest.java b/retail/interactive-tutorials/src/test/java/product/CrudProductTest.java new file mode 100644 index 00000000000..459f58ff6de --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/product/CrudProductTest.java @@ -0,0 +1,80 @@ +/* + * 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 product; + +import static com.google.common.truth.Truth.assertThat; +import static product.CreateProduct.createProduct; +import static product.CrudProduct.deleteProduct; +import static product.CrudProduct.getProduct; +import static product.CrudProduct.updateProduct; + +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.Product; +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class CrudProductTest { + + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + final String projectId = ServiceOptions.getDefaultProjectId(); + final String generatedProductId = UUID.randomUUID().toString(); + final String branchName = + String.format( + "projects/%s/locations/global/catalogs/default_catalog/branches/0", projectId); + final String productName = String.format("%s/products/%s", branchName, generatedProductId); + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + + Product createdProduct = createProduct(generatedProductId, branchName); + getProduct(productName); + updateProduct(createdProduct, productName); + deleteProduct(productName); + } + + @Test + public void testCrudProduct() { + String outputResult = bout.toString(); + + assertThat(outputResult).contains("Create product request"); + assertThat(outputResult).contains("Created product"); + assertThat(outputResult).contains("Get product response"); + assertThat(outputResult).contains("Update product request"); + assertThat(outputResult).contains("Updated product"); + assertThat(outputResult).contains("Delete product request name"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/src/test/java/product/DeleteProductTest.java b/retail/interactive-tutorials/src/test/java/product/DeleteProductTest.java new file mode 100644 index 00000000000..3ad0a5ba679 --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/product/DeleteProductTest.java @@ -0,0 +1,65 @@ +/* + * 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 product; + +import static com.google.common.truth.Truth.assertThat; +import static product.DeleteProduct.deleteProduct; +import static setup.SetupCleanup.createProduct; + +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class DeleteProductTest { + + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + final String generatedProductId = UUID.randomUUID().toString(); + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + + String createdProductName = createProduct(generatedProductId).getName(); + deleteProduct(createdProductName); + } + + @Test + public void testDeleteProduct() { + String outputResult = bout.toString(); + + assertThat(outputResult).contains("Delete product request"); + assertThat(outputResult).contains("was deleted"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/src/test/java/product/GetProductTest.java b/retail/interactive-tutorials/src/test/java/product/GetProductTest.java new file mode 100644 index 00000000000..c1a48e01033 --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/product/GetProductTest.java @@ -0,0 +1,69 @@ +/* + * 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 product; + +import static com.google.common.truth.Truth.assertThat; +import static product.CrudProduct.getProduct; +import static setup.SetupCleanup.createProduct; +import static setup.SetupCleanup.deleteProduct; + +import com.google.cloud.retail.v2.Product; +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class GetProductTest { + + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + final String generatedProductId = UUID.randomUUID().toString(); + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + + Product createdProduct = createProduct(generatedProductId); + Product product = getProduct(createdProduct.getName()); + deleteProduct(product.getName()); + } + + @Test + public void testGetProduct() { + String outputResult = bout.toString(); + + assertThat(outputResult).contains("Create product request"); + assertThat(outputResult).contains("Created product"); + assertThat(outputResult).contains("Get product response"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/src/test/java/product/ImportProductsBigQueryTableTest.java b/retail/interactive-tutorials/src/test/java/product/ImportProductsBigQueryTableTest.java new file mode 100644 index 00000000000..fce668a3fd9 --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/product/ImportProductsBigQueryTableTest.java @@ -0,0 +1,88 @@ +/* + * 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 product; + +import static com.google.common.truth.Truth.assertThat; +import static product.ImportProductsBigQueryTable.importProductsFromBigQuery; + +import com.google.cloud.ServiceOptions; +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ImportProductsBigQueryTableTest { + + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + } + + @Test + public void testValidImportProductsBigQueryTable() throws IOException, InterruptedException { + String projectId = ServiceOptions.getDefaultProjectId(); + String branchName = + String.format( + "projects/%s/locations/global/catalogs/default_catalog/branches/0", projectId); + String datasetId = "products"; + String tableId = "products"; + + importProductsFromBigQuery(projectId, branchName, datasetId, tableId); + + String outputResult = bout.toString(); + + assertThat(outputResult).contains("Import products from big query table request"); + assertThat(outputResult).contains("Number of successfully imported products:"); + assertThat(outputResult).contains("Number of failures during the importing: 0"); + } + + @Test + public void testInvalidImportProductsBigQueryTable() throws IOException, InterruptedException { + String projectId = ServiceOptions.getDefaultProjectId(); + String branchName = + String.format( + "projects/%s/locations/global/catalogs/default_catalog/branches/0", projectId); + String datasetId = "products"; + String tableId = "products_some_invalid"; + + importProductsFromBigQuery(projectId, branchName, datasetId, tableId); + + String outputResult = bout.toString(); + + assertThat(outputResult).contains("Import products from big query table request"); + assertThat(outputResult).contains("Number of successfully imported products:"); + assertThat(outputResult).contains("Number of failures during the importing:"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/src/test/java/product/ImportProductsGcsTest.java b/retail/interactive-tutorials/src/test/java/product/ImportProductsGcsTest.java new file mode 100644 index 00000000000..e2440695589 --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/product/ImportProductsGcsTest.java @@ -0,0 +1,72 @@ +/* + * 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 product; + +import static com.google.common.truth.Truth.assertThat; +import static product.ImportProductsGcs.importProductsFromGcs; + +import com.google.cloud.ServiceOptions; +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import product.setup.ProductsCreateGcsBucket; + +@RunWith(JUnit4.class) +public class ImportProductsGcsTest { + + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + ProductsCreateGcsBucket.main(); + final String projectId = ServiceOptions.getDefaultProjectId(); + final String branchName = + String.format( + "projects/%s/locations/global/catalogs/default_catalog/branches/0", projectId); + final String bucketName = ProductsCreateGcsBucket.getBucketName(); + final String gcsBucket = String.format("gs://%s", bucketName); + final String gscProductsObject = "products.json"; + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + + importProductsFromGcs(branchName, gcsBucket, gscProductsObject); + } + + @Test + public void testValidImportProductsGcs() { + String outputResult = bout.toString(); + + assertThat(outputResult).contains("Import products from google cloud source request"); + assertThat(outputResult).contains("Number of successfully imported products:"); + assertThat(outputResult).contains("Number of failures during the importing: 0"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/src/test/java/product/ImportProductsInlineSourceTest.java b/retail/interactive-tutorials/src/test/java/product/ImportProductsInlineSourceTest.java new file mode 100644 index 00000000000..7907e8d2ab8 --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/product/ImportProductsInlineSourceTest.java @@ -0,0 +1,67 @@ +/* + * 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 product; + +import static com.google.common.truth.Truth.assertThat; +import static product.ImportProductsInlineSource.importProductsInlineSource; + +import com.google.cloud.ServiceOptions; +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ImportProductsInlineSourceTest { + + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + final String projectId = ServiceOptions.getDefaultProjectId(); + final String branchName = + String.format( + "projects/%s/locations/global/catalogs/default_catalog/branches/0", projectId); + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + + importProductsInlineSource(branchName); + } + + @Test + public void testImportProductsInlineSource() { + String outputResult = bout.toString(); + + assertThat(outputResult).contains("Import products from inline source request"); + assertThat(outputResult).contains("Number of successfully imported products"); + assertThat(outputResult).contains("Number of failures during the importing"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/src/test/java/product/RemoveFulfillmentPlacesTest.java b/retail/interactive-tutorials/src/test/java/product/RemoveFulfillmentPlacesTest.java new file mode 100644 index 00000000000..b729d5eb937 --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/product/RemoveFulfillmentPlacesTest.java @@ -0,0 +1,77 @@ +/* + * 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 product; + +import static com.google.common.truth.Truth.assertThat; +import static product.RemoveFulfillmentPlaces.removeFulfillmentPlaces; +import static setup.SetupCleanup.createProduct; +import static setup.SetupCleanup.deleteProduct; +import static setup.SetupCleanup.getProduct; + +import com.google.cloud.ServiceOptions; +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class RemoveFulfillmentPlacesTest { + + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + final String projectId = ServiceOptions.getDefaultProjectId(); + final String generatedProductId = UUID.randomUUID().toString(); + final String productName = + String.format( + "projects/%s/locations/global/catalogs/default_catalog/branches/0/products/%s", + projectId, generatedProductId); + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + + createProduct(generatedProductId); + removeFulfillmentPlaces(productName, "store0"); + getProduct(productName); + deleteProduct(productName); + } + + @Test + public void testRemoveFulfillmentPlaces() { + String outputResult = bout.toString(); + + assertThat(outputResult).contains("Remove fulfilment places with current date"); + assertThat(outputResult).contains("Waiting for operation to finish..."); + assertThat(outputResult).contains("Delete product request name"); + assertThat(outputResult).contains("was deleted"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/src/test/java/product/SetInventoryTest.java b/retail/interactive-tutorials/src/test/java/product/SetInventoryTest.java new file mode 100644 index 00000000000..fdd1a685541 --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/product/SetInventoryTest.java @@ -0,0 +1,75 @@ +/* + * 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 product; + +import static com.google.common.truth.Truth.assertThat; +import static product.SetInventory.setInventory; +import static setup.SetupCleanup.createProduct; +import static setup.SetupCleanup.deleteProduct; +import static setup.SetupCleanup.getProduct; + +import com.google.cloud.ServiceOptions; +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class SetInventoryTest { + + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + final String projectId = ServiceOptions.getDefaultProjectId(); + final String generatedProductId = UUID.randomUUID().toString(); + final String productName = + String.format( + "projects/%s/locations/global/catalogs/default_catalog/branches/0/products/%s", + projectId, generatedProductId); + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + + createProduct(generatedProductId); + setInventory(productName); + getProduct(productName); + deleteProduct(productName); + } + + @Test + public void testSetInventoryTest() { + String outputResult = bout.toString(); + + assertThat(outputResult).contains("Set inventory request"); + assertThat(outputResult).contains("Waiting for operation to finish..."); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/src/test/java/product/UpdateProductTest.java b/retail/interactive-tutorials/src/test/java/product/UpdateProductTest.java new file mode 100644 index 00000000000..60e99a59d6c --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/product/UpdateProductTest.java @@ -0,0 +1,73 @@ +/* + * 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 product; + +import static com.google.common.truth.Truth.assertThat; +import static product.UpdateProduct.updateProduct; +import static setup.SetupCleanup.createProduct; +import static setup.SetupCleanup.deleteProduct; + +import com.google.cloud.ServiceOptions; +import com.google.cloud.retail.v2.Product; +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class UpdateProductTest { + + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + final String projectId = ServiceOptions.getDefaultProjectId(); + final String branchName = + String.format( + "projects/%s/locations/global/catalogs/default_catalog/branches/0", projectId); + final String generatedProductId = UUID.randomUUID().toString(); + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + + Product createdProduct = createProduct(generatedProductId); + updateProduct(createdProduct, branchName); + deleteProduct(createdProduct.getName()); + } + + @Test + public void testUpdateProduct() { + String outputResult = bout.toString(); + + assertThat(outputResult).contains("Update product request"); + assertThat(outputResult).contains("Updated product"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/src/test/java/resources/user_events.json b/retail/interactive-tutorials/src/test/java/resources/user_events.json new file mode 100644 index 00000000000..76d7c0df262 --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/resources/user_events.json @@ -0,0 +1,4 @@ +{"eventType":"home-page-view","visitorId":"bjbs_group1_visitor1","eventTime":"2022-04-13T10:27:42+00:00"} +{"eventType":"search","visitorId":"bjbs_group1_visitor1","eventTime":"2022-04-13T10:27:42+00:00","searchQuery":"RockerJeans teenagers blue jeans"} +{"eventType":"search","visitorId":"bjbs_group1_visitor1","eventTime":"2022-04-13T10:27:42+00:00","searchQuery":"SocksUnlimited teenagers black socks"} +{"eventType":"detail-page-view","visitorId":"bjbs_group1_visitor1","eventTime":"2022-04-13T10:27:42+00:00","productDetails":{"product":{"id":"GGCOGAEC100616"},"quantity":3}} \ No newline at end of file diff --git a/retail/interactive-tutorials/src/test/java/resources/user_events_some_invalid.json b/retail/interactive-tutorials/src/test/java/resources/user_events_some_invalid.json new file mode 100644 index 00000000000..27e00a0c878 --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/resources/user_events_some_invalid.json @@ -0,0 +1,4 @@ +{"eventType":"home-page-view","visitorId":"bjbs_group1_visitor1","eventTime":"2022-04-13T10:27:42+00:00"} +{"eventType":"invalid","visitorId":"bjbs_group1_visitor1","eventTime":"2022-04-13T10:27:42+00:00","searchQuery":"RockerJeans teenagers blue jeans"} +{"eventType":"search","visitorId":"bjbs_group1_visitor1","eventTime":"2022-04-13T10:27:42+00:00","searchQuery":"SocksUnlimited teenagers black socks"} +{"eventType":"detail-page-view","visitorId":"bjbs_group1_visitor1","eventTime":"2022-04-13T10:27:42+00:00","productDetails":{"product":{"id":"GGCOGAEC100616"},"quantity":3}} diff --git a/retail/interactive-tutorials/src/test/java/search/SearchSimpleQueryTest.java b/retail/interactive-tutorials/src/test/java/search/SearchSimpleQueryTest.java new file mode 100644 index 00000000000..bbf6f6b78f5 --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/search/SearchSimpleQueryTest.java @@ -0,0 +1,66 @@ +/* + * 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 search; + +import static com.google.common.truth.Truth.assertThat; +import static search.SearchSimpleQuery.searchResponse; + +import com.google.cloud.ServiceOptions; +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class SearchSimpleQueryTest { + + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + final String projectId = ServiceOptions.getDefaultProjectId(); + final String defaultCatalogName = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + final String defaultSearchPlacementName = defaultCatalogName + "/placements/default_search"; + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + + searchResponse(defaultSearchPlacementName); + } + + @Test + public void testOutput() { + String outputResult = bout.toString(); + + assertThat(outputResult).contains("Search request"); + assertThat(outputResult).contains("Search response"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/src/test/java/search/SearchWithBoostSpecTest.java b/retail/interactive-tutorials/src/test/java/search/SearchWithBoostSpecTest.java new file mode 100644 index 00000000000..8d6bb221070 --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/search/SearchWithBoostSpecTest.java @@ -0,0 +1,65 @@ +/* + * 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 search; + +import static com.google.common.truth.Truth.assertThat; +import static search.SearchWithBoostSpec.searchResponse; + +import com.google.cloud.ServiceOptions; +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class SearchWithBoostSpecTest { + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + final String projectId = ServiceOptions.getDefaultProjectId(); + final String defaultCatalogName = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + final String defaultSearchPlacementName = defaultCatalogName + "/placements/default_search"; + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + + searchResponse(defaultSearchPlacementName); + } + + @Test + public void testOutput() { + String outputResult = bout.toString(); + + assertThat(outputResult).contains("Search request"); + assertThat(outputResult).contains("Search response"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/src/test/java/search/SearchWithFacetSpecTest.java b/retail/interactive-tutorials/src/test/java/search/SearchWithFacetSpecTest.java new file mode 100644 index 00000000000..2f5e14112fc --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/search/SearchWithFacetSpecTest.java @@ -0,0 +1,65 @@ +/* + * 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 search; + +import static com.google.common.truth.Truth.assertThat; +import static search.SearchWithFacetSpec.searchResponse; + +import com.google.cloud.ServiceOptions; +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class SearchWithFacetSpecTest { + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + final String projectId = ServiceOptions.getDefaultProjectId(); + final String defaultCatalogName = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + final String defaultSearchPlacementName = defaultCatalogName + "/placements/default_search"; + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + + searchResponse(defaultSearchPlacementName); + } + + @Test + public void testOutput() { + String outputResult = bout.toString(); + + assertThat(outputResult).contains("Search request"); + assertThat(outputResult).contains("Search response"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/src/test/java/search/SearchWithFilteringTest.java b/retail/interactive-tutorials/src/test/java/search/SearchWithFilteringTest.java new file mode 100644 index 00000000000..0f91119dc56 --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/search/SearchWithFilteringTest.java @@ -0,0 +1,66 @@ +/* + * 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 search; + +import static com.google.common.truth.Truth.assertThat; +import static search.SearchWithFiltering.searchResponse; + +import com.google.cloud.ServiceOptions; +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class SearchWithFilteringTest { + + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + final String projectId = ServiceOptions.getDefaultProjectId(); + final String defaultCatalogName = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + final String defaultSearchPlacementName = defaultCatalogName + "/placements/default_search"; + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + + searchResponse(defaultSearchPlacementName); + } + + @Test + public void testOutput() { + String outputResult = bout.toString(); + + assertThat(outputResult).contains("Search request"); + assertThat(outputResult).contains("Search response"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/src/test/java/search/SearchWithOrderingTest.java b/retail/interactive-tutorials/src/test/java/search/SearchWithOrderingTest.java new file mode 100644 index 00000000000..ef7729244a2 --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/search/SearchWithOrderingTest.java @@ -0,0 +1,66 @@ +/* + * 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 search; + +import static com.google.common.truth.Truth.assertThat; +import static search.SearchWithOrdering.searchResponse; + +import com.google.cloud.ServiceOptions; +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class SearchWithOrderingTest { + + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + final String projectId = ServiceOptions.getDefaultProjectId(); + final String defaultCatalogName = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + final String defaultSearchPlacementName = defaultCatalogName + "/placements/default_search"; + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + + searchResponse(defaultSearchPlacementName); + } + + @Test + public void testOutput() { + String outputResult = bout.toString(); + + assertThat(outputResult).contains("Search request"); + assertThat(outputResult).contains("Search response"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/src/test/java/search/SearchWithPaginationTest.java b/retail/interactive-tutorials/src/test/java/search/SearchWithPaginationTest.java new file mode 100644 index 00000000000..92673441d78 --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/search/SearchWithPaginationTest.java @@ -0,0 +1,66 @@ +/* + * 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 search; + +import static com.google.common.truth.Truth.assertThat; +import static search.SearchWithPagination.searchResponse; + +import com.google.cloud.ServiceOptions; +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class SearchWithPaginationTest { + + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + final String projectId = ServiceOptions.getDefaultProjectId(); + final String defaultCatalogName = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + final String defaultSearchPlacementName = defaultCatalogName + "/placements/default_search"; + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + + searchResponse(defaultSearchPlacementName); + } + + @Test + public void testOutput() { + String outputResult = bout.toString(); + + assertThat(outputResult).contains("Search request"); + assertThat(outputResult).contains("Search response"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/src/test/java/search/SearchWithQueryExpansionSpecTest.java b/retail/interactive-tutorials/src/test/java/search/SearchWithQueryExpansionSpecTest.java new file mode 100644 index 00000000000..b4516c255dd --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/search/SearchWithQueryExpansionSpecTest.java @@ -0,0 +1,66 @@ +/* + * 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 search; + +import static com.google.common.truth.Truth.assertThat; +import static search.SearchWithQueryExpansionSpec.searchResponse; + +import com.google.cloud.ServiceOptions; +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class SearchWithQueryExpansionSpecTest { + + private ByteArrayOutputStream bout; + private PrintStream originalPrintStream; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + final String projectId = ServiceOptions.getDefaultProjectId(); + final String defaultCatalogName = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + final String defaultSearchPlacementName = defaultCatalogName + "/placements/default_search"; + bout = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(bout); + originalPrintStream = System.out; + System.setOut(out); + + searchResponse(defaultSearchPlacementName); + } + + @Test + public void testOutput() { + String outputResult = bout.toString(); + + assertThat(outputResult).contains("Search request"); + assertThat(outputResult).contains("Search response"); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } +} diff --git a/retail/interactive-tutorials/user_environment_setup.sh b/retail/interactive-tutorials/user_environment_setup.sh new file mode 100644 index 00000000000..0801f830c68 --- /dev/null +++ b/retail/interactive-tutorials/user_environment_setup.sh @@ -0,0 +1,96 @@ +#!/bin/bash + +# Copyright 2022 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +failure() { + echo "=========================================" + echo "The Google Cloud setup was not completed." + echo "Please fix the errors above!" + echo "=========================================" + exit 0 +} + +# catch any error that happened during execution +trap 'failure' ERR + +# set the Google Cloud Project ID + +project_id=$1 +echo "Project ID: $project_id" +gcloud config set project "$project_id" + +email=$(gcloud auth list --filter="status:ACTIVE account:$project_id.iam.gserviceaccount.com" --format="value(account)") +echo $email + +# Check if user has service account active +if [ -z "$email" ] +then + # Create a new service account + timestamp=$(date +%s) + + service_account_id="service-acc-$timestamp" + echo "Service Account: $service_account_id" + + # create service account (your service-acc-$timestamp) + gcloud iam service-accounts create "$service_account_id" +else + service_account_id="${email%@*}" + # Log out of service account + gcloud auth revoke 2>/dev/null +fi +echo "$service_account_id" + +editor=$(gcloud projects get-iam-policy $project_id \ +--flatten="bindings[].members" \ +--format='table(bindings.role)' \ +--filter="bindings.members:$service_account_id ROLE=roles/editor") + +retail_admin=$(gcloud projects get-iam-policy $project_id \ +--flatten="bindings[].members" \ +--format='table(bindings.role)' \ +--filter="bindings.members:$service_account_id ROLE=roles/retail.admin") + + +# assign necessary roles to your new service account +# Check if any of the needed roles is missing +if [ -z "$editor" ] || [ -z "$retail_admin" ] +then + # Assign necessary roles to your new service account. + for role in {retail.admin,editor} + do + gcloud projects add-iam-policy-binding "$project_id" --member="serviceAccount:$service_account_id@$project_id.iam.gserviceaccount.com" --role=roles/"${role}" + done + echo "Wait ~60 seconds to be sure the appropriate roles have been assigned to your service account" + sleep 60 +fi + +# upload your service account key file +service_acc_email="$service_account_id@$project_id.iam.gserviceaccount.com" +gcloud iam service-accounts keys create ~/key.json --iam-account "$service_acc_email" + +# activate the service account using the key +gcloud auth activate-service-account --key-file ~/key.json + + +# install needed Google client libraries +cd ~/cloudshell_open/java-docs-samples/retail/interactive-tutorials || exit +mvn clean install -DskipTests + + +# Print success message +echo "========================================" +echo "The Google Cloud setup is completed." +echo "Please proceed with the Tutorial steps" +echo "========================================" diff --git a/retail/interactive-tutorials/user_import_data_to_catalog.sh b/retail/interactive-tutorials/user_import_data_to_catalog.sh new file mode 100644 index 00000000000..e85d1cb9494 --- /dev/null +++ b/retail/interactive-tutorials/user_import_data_to_catalog.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# Copyright 2022 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{ + # set the key as GOOGLE_APPLICATION_CREDENTIALS + export GOOGLE_APPLICATION_CREDENTIALS=~/key.json + + # Change the working directory + cd ~/cloudshell_open/java-docs-samples/retail/interactive-tutorials/ || exit + + # Run the sample for creating the GCS bucket and extract the output of that execution + output=$(mvn compile exec:java -Dexec.mainClass="product.setup.ProductsCreateGcsBucket") + + # Get the bucket name and store it in the env variable BUCKET_NAME + temp="${output#*gcs bucket }" + bucket_name="${temp% was created*}" + export BUCKET_NAME=$bucket_name + + # Import products to the Retail catalog + mvn compile exec:java -Dexec.mainClass="product.ImportProductsGcs" + +} && { + + # Print success message + echo "=====================================" + echo "Your Retail catalog is ready to use!" + echo "=====================================" + + } || { + + # Print error message + echo "=====================================" + echo "Your Retail catalog wasn't created! Please fix the errors above!" + echo "=====================================" + + } diff --git a/security-command-center/snippets/pom.xml b/security-command-center/snippets/pom.xml index debfa2d1088..50edca6b652 100644 --- a/security-command-center/snippets/pom.xml +++ b/security-command-center/snippets/pom.xml @@ -32,7 +32,7 @@ com.google.cloud libraries-bom - 26.1.4 + 26.1.3 pom import @@ -43,7 +43,7 @@ com.google.cloud google-cloud-securitycenter - 2.13.0 + 2.11.1 diff --git a/security-command-center/snippets/src/main/java/NotificationReceiver.java b/security-command-center/snippets/src/main/java/NotificationReceiver.java new file mode 100644 index 00000000000..a0988edce36 --- /dev/null +++ b/security-command-center/snippets/src/main/java/NotificationReceiver.java @@ -0,0 +1,75 @@ +/* + * 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. + */ + +// [START securitycenter_receive_notifications] + +import com.google.cloud.pubsub.v1.AckReplyConsumer; +import com.google.cloud.pubsub.v1.MessageReceiver; +import com.google.cloud.pubsub.v1.Subscriber; +import com.google.cloud.securitycenter.v1.NotificationMessage; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; +import com.google.pubsub.v1.ProjectSubscriptionName; +import com.google.pubsub.v1.PubsubMessage; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class NotificationReceiver { + + private NotificationReceiver() { + } + + public static void receiveNotificationMessages(String projectId, String subscriptionId) { + // String projectId = "{your-project}"; + // String subscriptionId = "{your-subscription}"; + ProjectSubscriptionName subscriptionName = + ProjectSubscriptionName.of(projectId, subscriptionId); + + try { + Subscriber subscriber = + Subscriber.newBuilder(subscriptionName, new NotificationMessageReceiver()).build(); + subscriber.startAsync().awaitRunning(); + + // This sets the timeout value of the subscriber to 10s. + subscriber.awaitTerminated(10_000, TimeUnit.MILLISECONDS); + } catch (IllegalStateException | TimeoutException e) { + System.out.println("Subscriber stopped: " + e); + } + } + + static class NotificationMessageReceiver implements MessageReceiver { + + @Override + public void receiveMessage(PubsubMessage message, AckReplyConsumer consumer) { + NotificationMessage.Builder notificationMessageBuilder = NotificationMessage.newBuilder(); + + try { + String jsonString = message.getData().toStringUtf8(); + JsonFormat.parser().merge(jsonString, notificationMessageBuilder); + + NotificationMessage notificationMessage = notificationMessageBuilder.build(); + System.out.println( + String.format("Config id: %s", notificationMessage.getNotificationConfigName())); + System.out.println(String.format("Finding: %s", notificationMessage.getFinding())); + } catch (InvalidProtocolBufferException e) { + System.out.println("Could not parse message: " + e); + } finally { + consumer.ack(); + } + } + } +} +// [END securitycenter_receive_notifications] diff --git a/security-command-center/snippets/src/main/java/bigqueryexport/CreateBigQueryExport.java b/security-command-center/snippets/src/main/java/bigqueryexport/CreateBigQueryExport.java new file mode 100644 index 00000000000..5a55cf95f7d --- /dev/null +++ b/security-command-center/snippets/src/main/java/bigqueryexport/CreateBigQueryExport.java @@ -0,0 +1,89 @@ +/* + * 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 bigqueryexport; + +// [START securitycenter_create_bigquery_export] + +import com.google.cloud.securitycenter.v1.BigQueryExport; +import com.google.cloud.securitycenter.v1.CreateBigQueryExportRequest; +import com.google.cloud.securitycenter.v1.SecurityCenterClient; +import java.io.IOException; +import java.util.UUID; + +public class CreateBigQueryExport { + + public static void main(String[] args) throws IOException { + // TODO(Developer): Modify the following variable values. + + // parent: Use any one of the following resource paths: + // - organizations/{organization_id} + // - folders/{folder_id} + // - projects/{project_id} + String parent = String.format("projects/%s", "your-google-cloud-project-id"); + + // filter: Expression that defines the filter to apply across create/update events of findings. + String filter = + "severity=\"LOW\" OR severity=\"MEDIUM\" AND " + + "category=\"Persistence: IAM Anomalous Grant\" AND " + + "-resource.type:\"compute\""; + + // bigQueryDatasetId: The BigQuery dataset to write findings' updates to. + String bigQueryDatasetId = "your-bigquery-dataset-id"; + + // bigQueryExportId: Unique identifier provided by the client. + // For more info, see: + // https://cloud.google.com/security-command-center/docs/how-to-analyze-findings-in-big-query#export_findings_from_to + String bigQueryExportId = "default-" + UUID.randomUUID().toString().split("-")[0]; + + createBigQueryExport(parent, filter, bigQueryDatasetId, bigQueryExportId); + } + + // Create export configuration to export findings from a project to a BigQuery dataset. + // Optionally specify filter to export certain findings only. + public static void createBigQueryExport( + String parent, String filter, String bigQueryDatasetId, String bigQueryExportId) + 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 (SecurityCenterClient client = SecurityCenterClient.create()) { + + // Create the BigQuery export configuration. + BigQueryExport bigQueryExport = + BigQueryExport.newBuilder() + .setDescription( + "Export low and medium findings if the compute resource " + + "has an IAM anomalous grant") + .setFilter(filter) + .setDataset(String.format("%s/datasets/%s", parent, bigQueryDatasetId)) + .build(); + + CreateBigQueryExportRequest bigQueryExportRequest = + CreateBigQueryExportRequest.newBuilder() + .setParent(parent) + .setBigQueryExport(bigQueryExport) + .setBigQueryExportId(bigQueryExportId) + .build(); + + // Create the export request. + BigQueryExport response = client.createBigQueryExport(bigQueryExportRequest); + + System.out.printf("BigQuery export request created successfully: %s\n", response.getName()); + } + } +} +// [END securitycenter_create_bigquery_export] diff --git a/security-command-center/snippets/src/main/java/bigqueryexport/DeleteBigQueryExport.java b/security-command-center/snippets/src/main/java/bigqueryexport/DeleteBigQueryExport.java new file mode 100644 index 00000000000..389ca93cb93 --- /dev/null +++ b/security-command-center/snippets/src/main/java/bigqueryexport/DeleteBigQueryExport.java @@ -0,0 +1,60 @@ +/* + * 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 bigqueryexport; + +// [START securitycenter_delete_bigquery_export] + +import com.google.cloud.securitycenter.v1.DeleteBigQueryExportRequest; +import com.google.cloud.securitycenter.v1.SecurityCenterClient; +import java.io.IOException; + +public class DeleteBigQueryExport { + + public static void main(String[] args) throws IOException { + // TODO(Developer): Modify the following variable values. + + // parent: Use any one of the following resource paths: + // - organizations/{organization_id} + // - folders/{folder_id} + // - projects/{project_id} + String parent = String.format("projects/%s", "your-google-cloud-project-id"); + + // bigQueryExportId: Unique identifier that is used to identify the export. + String bigQueryExportId = "export-id"; + + deleteBigQueryExport(parent, bigQueryExportId); + } + + // Delete an existing BigQuery export. + public static void deleteBigQueryExport(String parent, String bigQueryExportId) + 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 (SecurityCenterClient client = SecurityCenterClient.create()) { + + DeleteBigQueryExportRequest bigQueryExportRequest = + DeleteBigQueryExportRequest.newBuilder() + .setName(String.format("%s/bigQueryExports/%s", parent, bigQueryExportId)) + .build(); + + client.deleteBigQueryExport(bigQueryExportRequest); + System.out.printf("BigQuery export request deleted successfully: %s", bigQueryExportId); + } + } +} +// [END securitycenter_delete_bigquery_export] diff --git a/security-command-center/snippets/src/main/java/bigqueryexport/GetBigQueryExport.java b/security-command-center/snippets/src/main/java/bigqueryexport/GetBigQueryExport.java new file mode 100644 index 00000000000..49d91b710a2 --- /dev/null +++ b/security-command-center/snippets/src/main/java/bigqueryexport/GetBigQueryExport.java @@ -0,0 +1,60 @@ +/* + * 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 bigqueryexport; + +// [START securitycenter_get_bigquery_export] + +import com.google.cloud.securitycenter.v1.BigQueryExport; +import com.google.cloud.securitycenter.v1.GetBigQueryExportRequest; +import com.google.cloud.securitycenter.v1.SecurityCenterClient; +import java.io.IOException; + +public class GetBigQueryExport { + + public static void main(String[] args) throws IOException { + // TODO(Developer): Modify the following variable values. + + // parent: Use any one of the following resource paths: + // - organizations/{organization_id} + // - folders/{folder_id} + // - projects/{project_id} + String parent = String.format("projects/%s", "your-google-cloud-project-id"); + + // bigQueryExportId: Unique identifier that is used to identify the export. + String bigQueryExportId = "export-id"; + + getBigQueryExport(parent, bigQueryExportId); + } + + // Retrieve an existing BigQuery export. + public static void getBigQueryExport(String parent, String bigQueryExportId) 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 (SecurityCenterClient client = SecurityCenterClient.create()) { + + GetBigQueryExportRequest bigQueryExportRequest = + GetBigQueryExportRequest.newBuilder() + .setName(String.format("%s/bigQueryExports/%s", parent, bigQueryExportId)) + .build(); + + BigQueryExport response = client.getBigQueryExport(bigQueryExportRequest); + System.out.printf("Retrieved the BigQuery export: %s", response.getName()); + } + } +} +// [END securitycenter_get_bigquery_export] diff --git a/security-command-center/snippets/src/main/java/bigqueryexport/ListBigQueryExports.java b/security-command-center/snippets/src/main/java/bigqueryexport/ListBigQueryExports.java new file mode 100644 index 00000000000..37bf49b0198 --- /dev/null +++ b/security-command-center/snippets/src/main/java/bigqueryexport/ListBigQueryExports.java @@ -0,0 +1,61 @@ +/* + * 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 bigqueryexport; + +// [START securitycenter_list_bigquery_export] + +import com.google.cloud.securitycenter.v1.BigQueryExport; +import com.google.cloud.securitycenter.v1.ListBigQueryExportsRequest; +import com.google.cloud.securitycenter.v1.SecurityCenterClient; +import com.google.cloud.securitycenter.v1.SecurityCenterClient.ListBigQueryExportsPagedResponse; +import java.io.IOException; + +public class ListBigQueryExports { + + public static void main(String[] args) throws IOException { + // TODO(Developer): Modify the following variable values. + + // parent: The parent, which owns the collection of BigQuery exports. + // Use any one of the following resource paths: + // - organizations/{organization_id} + // - folders/{folder_id} + // - projects/{project_id} + String parent = String.format("projects/%s", "your-google-cloud-project-id"); + + listBigQueryExports(parent); + } + + // List BigQuery exports in the given parent. + public static void listBigQueryExports(String parent) 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 (SecurityCenterClient client = SecurityCenterClient.create()) { + + ListBigQueryExportsRequest request = + ListBigQueryExportsRequest.newBuilder().setParent(parent).build(); + + ListBigQueryExportsPagedResponse response = client.listBigQueryExports(request); + + System.out.println("Listing BigQuery exports:"); + for (BigQueryExport bigQueryExport : response.iterateAll()) { + System.out.println(bigQueryExport.getName()); + } + } + } +} +// [END securitycenter_list_bigquery_export] diff --git a/security-command-center/snippets/src/main/java/bigqueryexport/UpdateBigQueryExport.java b/security-command-center/snippets/src/main/java/bigqueryexport/UpdateBigQueryExport.java new file mode 100644 index 00000000000..6c2c94b379e --- /dev/null +++ b/security-command-center/snippets/src/main/java/bigqueryexport/UpdateBigQueryExport.java @@ -0,0 +1,86 @@ +/* + * 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 bigqueryexport; + +// [START securitycenter_update_bigquery_export] + +import com.google.cloud.securitycenter.v1.BigQueryExport; +import com.google.cloud.securitycenter.v1.SecurityCenterClient; +import com.google.cloud.securitycenter.v1.UpdateBigQueryExportRequest; +import com.google.protobuf.FieldMask; +import java.io.IOException; + +public class UpdateBigQueryExport { + + public static void main(String[] args) throws IOException { + // TODO(Developer): Modify the following variable values. + + // parent: Use any one of the following resource paths: + // - organizations/{organization_id} + // - folders/{folder_id} + // - projects/{project_id} + String parent = String.format("projects/%s", "your-google-cloud-project-id"); + + // filter: Expression that defines the filter to apply across create/update events of findings. + String filter = + "severity=\"LOW\" OR severity=\"MEDIUM\" AND " + + "category=\"Persistence: IAM Anomalous Grant\" AND " + + "-resource.type:\"compute\""; + + // bigQueryExportId: Unique identifier provided by the client. + // For more info, see: + // https://cloud.google.com/security-command-center/docs/how-to-analyze-findings-in-big-query#export_findings_from_to + String bigQueryExportId = "big-query-export-id"; + + updateBigQueryExport(parent, filter, bigQueryExportId); + } + + // Updates an existing BigQuery export. + public static void updateBigQueryExport(String parent, String filter, String bigQueryExportId) + 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 (SecurityCenterClient client = SecurityCenterClient.create()) { + + // Set the new values for export configuration. + BigQueryExport bigQueryExport = + BigQueryExport.newBuilder() + .setName(String.format("%s/bigQueryExports/%s", parent, bigQueryExportId)) + .setFilter(filter) + .build(); + + UpdateBigQueryExportRequest request = + UpdateBigQueryExportRequest.newBuilder() + .setBigQueryExport(bigQueryExport) + // Set the update mask to specify which properties should be updated. + // If empty, all mutable fields will be updated. + // For more info on constructing field mask path, see the proto or: + // https://cloud.google.com/java/docs/reference/protobuf/latest/com.google.protobuf.FieldMask + .setUpdateMask(FieldMask.newBuilder().addPaths("filter").build()) + .build(); + + BigQueryExport response = client.updateBigQueryExport(request); + if (!response.getFilter().equalsIgnoreCase(filter)) { + System.out.println("Failed to update BigQueryExport!"); + return; + } + System.out.println("BigQueryExport updated successfully!"); + } + } +} +// [END securitycenter_update_bigquery_export] diff --git a/security-command-center/snippets/src/main/java/muteconfig/BulkMuteFindings.java b/security-command-center/snippets/src/main/java/muteconfig/BulkMuteFindings.java new file mode 100644 index 00000000000..3c86fadebc2 --- /dev/null +++ b/security-command-center/snippets/src/main/java/muteconfig/BulkMuteFindings.java @@ -0,0 +1,71 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package muteconfig; + +// [START securitycenter_bulk_mute] + +import com.google.cloud.securitycenter.v1.BulkMuteFindingsRequest; +import com.google.cloud.securitycenter.v1.BulkMuteFindingsResponse; +import com.google.cloud.securitycenter.v1.SecurityCenterClient; +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +public class BulkMuteFindings { + + public static void main(String[] args) { + // TODO: Replace the variables within {} + + // parentPath: Use any one of the following options: + // - organizations/{organization_id} + // - folders/{folder_id} + // - projects/{project_id} + String parentPath = String.format("projects/%s", "your-google-cloud-project-id"); + + // muteRule: Expression that identifies findings that should be muted. + // eg: "resource.project_display_name=\"PROJECT_ID\"" + String muteRule = "{filter-condition}"; + + bulkMute(parentPath, muteRule); + } + + // Kicks off a long-running operation (LRO) to bulk mute findings for a parent based on a filter. + // The parent can be either an organization, folder, or project. The findings + // matched by the filter will be muted after the LRO is done. + public static void bulkMute(String parentPath, String muteRule) { + // 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 (SecurityCenterClient client = SecurityCenterClient.create()) { + + BulkMuteFindingsRequest bulkMuteFindingsRequest = + BulkMuteFindingsRequest.newBuilder() + .setParent(parentPath) + // To create mute rules, see: + // https://cloud.google.com/security-command-center/docs/how-to-mute-findings#create_mute_rules + .setFilter(muteRule) + .build(); + + // ExecutionException is thrown if the below call fails. + BulkMuteFindingsResponse response = + client.bulkMuteFindingsAsync(bulkMuteFindingsRequest).get(); + System.out.println("Bulk mute findings completed successfully! " + response); + } catch (IOException | InterruptedException | ExecutionException e) { + System.out.println("Bulk mute findings failed! \n Exception: " + e); + } + } +} +// [END securitycenter_bulk_mute] diff --git a/security-command-center/snippets/src/main/java/muteconfig/CreateMuteRule.java b/security-command-center/snippets/src/main/java/muteconfig/CreateMuteRule.java new file mode 100644 index 00000000000..edd71053895 --- /dev/null +++ b/security-command-center/snippets/src/main/java/muteconfig/CreateMuteRule.java @@ -0,0 +1,79 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package muteconfig; + +// [START securitycenter_create_mute_config] + +import com.google.cloud.securitycenter.v1.CreateMuteConfigRequest; +import com.google.cloud.securitycenter.v1.MuteConfig; +import com.google.cloud.securitycenter.v1.SecurityCenterClient; +import java.io.IOException; +import java.util.UUID; + +public class CreateMuteRule { + + public static void main(String[] args) { + // TODO: Replace the variables within {} + + // parentPath: Use any one of the following options: + // - organizations/{organization_id} + // - folders/{folder_id} + // - projects/{project_id} + String parentPath = String.format("projects/%s", "your-google-cloud-project-id"); + + // muteConfigId: Set a random id; max of 63 chars. + String muteConfigId = "random-mute-id-" + UUID.randomUUID(); + createMuteRule(parentPath, muteConfigId); + } + + // Creates a mute configuration under a given scope that will mute + // all new findings that match a given filter. + // Existing findings will not be muted. + public static void createMuteRule(String parentPath, String muteConfigId) { + // 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 (SecurityCenterClient client = SecurityCenterClient.create()) { + + MuteConfig muteConfig = + MuteConfig.newBuilder() + .setDescription("Mute low-medium IAM grants excluding 'compute' ") + // Set mute rule(s). + // To construct mute rules and for supported properties, see: + // https://cloud.google.com/security-command-center/docs/how-to-mute-findings#create_mute_rules + .setFilter( + "severity=\"LOW\" OR severity=\"MEDIUM\" AND " + + "category=\"Persistence: IAM Anomalous Grant\" AND " + + "-resource.type:\"compute\"") + .build(); + + CreateMuteConfigRequest request = + CreateMuteConfigRequest.newBuilder() + .setParent(parentPath) + .setMuteConfigId(muteConfigId) + .setMuteConfig(muteConfig) + .build(); + + // ExecutionException is thrown if the below call fails. + MuteConfig response = client.createMuteConfig(request); + System.out.println("Mute rule created successfully: " + response.getName()); + } catch (IOException e) { + System.out.println("Mute rule creation failed! \n Exception: " + e); + } + } +} +// [END securitycenter_create_mute_config] diff --git a/security-command-center/snippets/src/main/java/muteconfig/DeleteMuteRule.java b/security-command-center/snippets/src/main/java/muteconfig/DeleteMuteRule.java new file mode 100644 index 00000000000..fd25de67407 --- /dev/null +++ b/security-command-center/snippets/src/main/java/muteconfig/DeleteMuteRule.java @@ -0,0 +1,60 @@ +/* + * 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 muteconfig; + +// [START securitycenter_delete_mute_config] + +import com.google.cloud.securitycenter.v1.MuteConfigName; +import com.google.cloud.securitycenter.v1.SecurityCenterClient; +import java.io.IOException; + +public class DeleteMuteRule { + + public static void main(String[] args) { + // TODO(Developer): Replace the following variables + // parentPath: Use any one of the following options: + // - organizations/{organization_id} + // - folders/{folder_id} + // - projects/{project_id} + String parentPath = String.format("projects/%s", "your-google-cloud-project-id"); + + // muteConfigId: Specify the name of the mute config to delete. + String muteConfigId = "mute-config-id"; + + deleteMuteRule(parentPath, muteConfigId); + } + + // Deletes a mute configuration given its resource name. + // Note: Previously muted findings are not affected when a mute config is deleted. + public static void deleteMuteRule(String projectId, String muteConfigId) { + // 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 (SecurityCenterClient client = SecurityCenterClient.create()) { + // Use appropriate MuteConfigName methods depending on the type of parent. + // org -> MuteConfigName.ofOrganizationMuteConfigName() + // folder -> MuteConfigName.ofFolderMuteConfigName() + // project -> MuteConfigName.ofProjectMuteConfigName) + client.deleteMuteConfig(MuteConfigName.ofProjectMuteConfigName(projectId, muteConfigId)); + + System.out.println("Mute rule deleted successfully: " + muteConfigId); + } catch (IOException e) { + System.out.println("Mute rule deletion failed! \n Exception: " + e); + } + } +} +// [END securitycenter_delete_mute_config] diff --git a/security-command-center/snippets/src/main/java/muteconfig/GetMuteRule.java b/security-command-center/snippets/src/main/java/muteconfig/GetMuteRule.java new file mode 100644 index 00000000000..0d32201262b --- /dev/null +++ b/security-command-center/snippets/src/main/java/muteconfig/GetMuteRule.java @@ -0,0 +1,62 @@ +/* + * 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 muteconfig; + +// [START securitycenter_get_mute_config] + +import com.google.cloud.securitycenter.v1.MuteConfig; +import com.google.cloud.securitycenter.v1.MuteConfigName; +import com.google.cloud.securitycenter.v1.SecurityCenterClient; +import java.io.IOException; + +public class GetMuteRule { + + public static void main(String[] args) { + // TODO(Developer): Replace the following variables + + // parentPath: Use any one of the following options: + // - organizations/{organization_id} + // - folders/{folder_id} + // - projects/{project_id} + String parentPath = String.format("projects/%s", "your-google-cloud-project-id"); + + // muteConfigId: Name of the mute config to retrieve. + String muteConfigId = "mute-config-id"; + + getMuteRule(parentPath, muteConfigId); + } + + // Retrieves a mute configuration given its resource name. + public static void getMuteRule(String projectId, String muteConfigId) { + // 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 (SecurityCenterClient client = SecurityCenterClient.create()) { + // Use appropriate MuteConfigName methods depending on the type of parent. + // (org -> MuteConfigName.ofOrganizationMuteConfigName() + // folder -> MuteConfigName.ofFolderMuteConfigName() + // project -> MuteConfigName.ofProjectMuteConfigName) + MuteConfig muteConfig = + client.getMuteConfig(MuteConfigName.ofProjectMuteConfigName(projectId, muteConfigId)); + + System.out.println("Retrieved the mute config: " + muteConfig); + } catch (IOException e) { + System.out.println("Mute rule retrieval failed! \n Exception: " + e); + } + } +} +// [END securitycenter_get_mute_config] diff --git a/security-command-center/snippets/src/main/java/muteconfig/ListMuteRules.java b/security-command-center/snippets/src/main/java/muteconfig/ListMuteRules.java new file mode 100644 index 00000000000..d922dab063f --- /dev/null +++ b/security-command-center/snippets/src/main/java/muteconfig/ListMuteRules.java @@ -0,0 +1,61 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package muteconfig; + +// [START securitycenter_list_mute_configs] + +import com.google.cloud.securitycenter.v1.ListMuteConfigsRequest; +import com.google.cloud.securitycenter.v1.MuteConfig; +import com.google.cloud.securitycenter.v1.SecurityCenterClient; +import java.io.IOException; + +public class ListMuteRules { + + public static void main(String[] args) { + // TODO: Replace variables enclosed within {} + + // parent: Use any one of the following resource paths to list mute configurations: + // - organizations/{organization_id} + // - folders/{folder_id} + // - projects/{project_id} + String parentPath = String.format("projects/%s", "your-google-cloud-project-id"); + listMuteRules(parentPath); + } + + // Listing mute configs at the organization level will return all the configs + // at the org, folder, and project levels. + // Similarly, listing configs at folder level will list all the configs + // at the folder and project levels. + public static void listMuteRules(String parent) { + // 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 (SecurityCenterClient client = SecurityCenterClient.create()) { + + ListMuteConfigsRequest listMuteConfigsRequest = + ListMuteConfigsRequest.newBuilder().setParent(parent).build(); + + // List all mute configs present in the resource. + for (MuteConfig muteConfig : client.listMuteConfigs(listMuteConfigsRequest).iterateAll()) { + System.out.println(muteConfig.getName()); + } + } catch (IOException e) { + System.out.println("Listing Mute rule failed! \n Exception: " + e); + } + } +} +// [END securitycenter_list_mute_configs] diff --git a/security-command-center/snippets/src/main/java/muteconfig/SetMuteFinding.java b/security-command-center/snippets/src/main/java/muteconfig/SetMuteFinding.java new file mode 100644 index 00000000000..ac63853e17a --- /dev/null +++ b/security-command-center/snippets/src/main/java/muteconfig/SetMuteFinding.java @@ -0,0 +1,62 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package muteconfig; + +// [START securitycenter_set_mute] + +import com.google.cloud.securitycenter.v1.Finding; +import com.google.cloud.securitycenter.v1.Finding.Mute; +import com.google.cloud.securitycenter.v1.SecurityCenterClient; +import com.google.cloud.securitycenter.v1.SetMuteRequest; +import java.io.IOException; + +public class SetMuteFinding { + + public static void main(String[] args) { + // TODO: Replace the variables within {} + + // findingPath: The relative resource name of the finding. See: + // https://cloud.google.com/apis/design/resource_names#relative_resource_name + // Use any one of the following formats: + // - organizations/{organization_id}/sources/{source_id}/finding/{finding_id} + // - folders/{folder_id}/sources/{source_id}/finding/{finding_id} + // - projects/{project_id}/sources/{source_id}/finding/{finding_id} + String findingPath = "{path-to-the-finding}"; + setMute(findingPath); + } + + // Mute an individual finding. + // If a finding is already muted, muting it again has no effect. + // Various mute states are: MUTE_UNSPECIFIED/MUTE/UNMUTE. + public static void setMute(String findingPath) { + // 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 (SecurityCenterClient client = SecurityCenterClient.create()) { + + SetMuteRequest setMuteRequest = + SetMuteRequest.newBuilder().setName(findingPath).setMute(Mute.MUTED).build(); + + Finding finding = client.setMute(setMuteRequest); + System.out.println( + "Mute value for the finding " + finding.getName() + " is: " + finding.getMute()); + } catch (IOException e) { + System.out.println("Failed to set the specified mute value. \n Exception: " + e); + } + } +} +// [END securitycenter_set_mute] diff --git a/security-command-center/snippets/src/main/java/muteconfig/SetUnmuteFinding.java b/security-command-center/snippets/src/main/java/muteconfig/SetUnmuteFinding.java new file mode 100644 index 00000000000..47447015948 --- /dev/null +++ b/security-command-center/snippets/src/main/java/muteconfig/SetUnmuteFinding.java @@ -0,0 +1,62 @@ +/* + * 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 muteconfig; + +// [START securitycenter_set_unmute] + +import com.google.cloud.securitycenter.v1.Finding; +import com.google.cloud.securitycenter.v1.Finding.Mute; +import com.google.cloud.securitycenter.v1.SecurityCenterClient; +import com.google.cloud.securitycenter.v1.SetMuteRequest; +import java.io.IOException; + +public class SetUnmuteFinding { + + public static void main(String[] args) { + // TODO: Replace the variables within {} + + // findingPath: The relative resource name of the finding. See: + // https://cloud.google.com/apis/design/resource_names#relative_resource_name + // Use any one of the following formats: + // - organizations/{organization_id}/sources/{source_id}/finding/{finding_id} + // - folders/{folder_id}/sources/{source_id}/finding/{finding_id} + // - projects/{project_id}/sources/{source_id}/finding/{finding_id} + String findingPath = "{path-to-the-finding}"; + setUnmute(findingPath); + } + + // Unmute an individual finding. + // Unmuting a finding that isn't muted has no effect. + // Various mute states are: MUTE_UNSPECIFIED/MUTE/UNMUTE. + public static void setUnmute(String findingPath) { + // 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 (SecurityCenterClient client = SecurityCenterClient.create()) { + + SetMuteRequest setMuteRequest = + SetMuteRequest.newBuilder().setName(findingPath).setMute(Mute.UNMUTED).build(); + + Finding finding = client.setMute(setMuteRequest); + System.out.println( + "Mute value for the finding " + finding.getName() + " is: " + finding.getMute()); + } catch (IOException e) { + System.out.println("Failed to set the specified mute value. \n Exception: " + e); + } + } +} +// [END securitycenter_set_unmute] diff --git a/security-command-center/snippets/src/main/java/muteconfig/UpdateMuteRule.java b/security-command-center/snippets/src/main/java/muteconfig/UpdateMuteRule.java new file mode 100644 index 00000000000..46225241b26 --- /dev/null +++ b/security-command-center/snippets/src/main/java/muteconfig/UpdateMuteRule.java @@ -0,0 +1,74 @@ +/* + * 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 muteconfig; + +// [START securitycenter_update_mute_config] + +import com.google.cloud.securitycenter.v1.MuteConfig; +import com.google.cloud.securitycenter.v1.SecurityCenterClient; +import com.google.cloud.securitycenter.v1.UpdateMuteConfigRequest; +import com.google.protobuf.FieldMask; +import java.io.IOException; + +public class UpdateMuteRule { + + public static void main(String[] args) { + // TODO: Replace the variables within {} + + // Specify the name of the mute config to delete. + // muteConfigName: Use any one of the following formats: + // - organizations/{organization}/muteConfigs/{config_id} + // - folders/{folder}/muteConfigs/{config_id} + // - projects/{project}/muteConfigs/{config_id} + String muteConfigName = "{any-one-of-the-above-formats}"; + updateMuteRule(muteConfigName); + } + + // Updates an existing mute configuration. + // The following can be updated in a mute config: description and filter. + public static void updateMuteRule(String muteConfigName) { + // 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 (SecurityCenterClient securityCenterClient = SecurityCenterClient.create()) { + + MuteConfig updateMuteConfig = + MuteConfig.newBuilder() + .setName(muteConfigName) + .setDescription("Updated mute config description") + .build(); + + UpdateMuteConfigRequest updateMuteConfigRequest = + UpdateMuteConfigRequest.newBuilder() + .setMuteConfig(updateMuteConfig) + // Set the update mask to specify which properties of the mute config should be + // updated. + // If empty, all mutable fields will be updated. + // Make sure that the mask fields match the properties changed in 'updateMuteConfig'. + // For more info on constructing update mask path, see the proto or: + // https://cloud.google.com/security-command-center/docs/reference/rest/v1/folders.muteConfigs/patch?hl=en#query-parameters + .setUpdateMask(FieldMask.newBuilder().addPaths("description").build()) + .build(); + + MuteConfig response = securityCenterClient.updateMuteConfig(updateMuteConfigRequest); + System.out.println(response); + } catch (IOException e) { + System.out.println("Mute rule update failed! \n Exception: " + e); + } + } +} +// [END securitycenter_update_mute_config] diff --git a/security-command-center/snippets/src/test/java/BigQueryExportIT.java b/security-command-center/snippets/src/test/java/BigQueryExportIT.java new file mode 100644 index 00000000000..cc5ecf97d18 --- /dev/null +++ b/security-command-center/snippets/src/test/java/BigQueryExportIT.java @@ -0,0 +1,155 @@ +/* + * 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. + */ + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import bigqueryexport.CreateBigQueryExport; +import bigqueryexport.DeleteBigQueryExport; +import bigqueryexport.GetBigQueryExport; +import bigqueryexport.ListBigQueryExports; +import bigqueryexport.UpdateBigQueryExport; +import com.google.cloud.bigquery.BigQuery; +import com.google.cloud.bigquery.BigQueryException; +import com.google.cloud.bigquery.BigQueryOptions; +import com.google.cloud.bigquery.Dataset; +import com.google.cloud.bigquery.DatasetInfo; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.UUID; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +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 BigQueryExportIT { + + // TODO(Developer): Replace the below variables. + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String BQ_DATASET_NAME = "sampledataset"; + private static final String BQ_EXPORT_ID = + "default-" + UUID.randomUUID().toString().split("-")[0]; + + private static ByteArrayOutputStream stdOut; + + // 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 { + final PrintStream out = System.out; + stdOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdOut)); + + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + + // Create a BigQuery dataset. + createBigQueryDataset(BQ_DATASET_NAME); + // Create export request. + String filter = "severity=\"LOW\" OR severity=\"MEDIUM\""; + CreateBigQueryExport.createBigQueryExport( + String.format("projects/%s", PROJECT_ID), filter, BQ_DATASET_NAME, BQ_EXPORT_ID); + + stdOut = null; + System.setOut(out); + } + + @AfterClass + public static void cleanUp() throws IOException { + final PrintStream out = System.out; + stdOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdOut)); + + // Delete BigQuery Dataset and export request. + deleteBigQueryDataset(BQ_DATASET_NAME); + DeleteBigQueryExport.deleteBigQueryExport( + String.format("projects/%s", PROJECT_ID), BQ_EXPORT_ID); + assertThat(stdOut.toString()) + .contains(String.format("BigQuery export request deleted successfully: %s", BQ_EXPORT_ID)); + + stdOut = null; + System.setOut(out); + } + + private static void createBigQueryDataset(String datasetName) { + try { + BigQuery bigquery = BigQueryOptions.getDefaultInstance().getService(); + + DatasetInfo datasetInfo = DatasetInfo.newBuilder(datasetName).build(); + + Dataset newDataset = bigquery.create(datasetInfo); + String newDatasetName = newDataset.getDatasetId().getDataset(); + System.out.println(newDatasetName + " created successfully"); + } catch (BigQueryException e) { + if (e.toString().contains("Already Exists: Dataset")) { + return; + } + Assert.fail("Dataset was not created. \n" + e); + } + } + + private static void deleteBigQueryDataset(String datasetName) { + try { + BigQuery bigquery = BigQueryOptions.getDefaultInstance().getService(); + Assert.assertTrue("Deleted BigQuery dataset", bigquery.delete(datasetName)); + } catch (BigQueryException e) { + Assert.fail("Dataset was not deleted. \n" + e); + } + } + + @Before + public void beforeEach() { + stdOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdOut)); + } + + @After + public void afterEach() { + stdOut = null; + System.setOut(null); + } + + @Test + public void testGetBigQueryExport() throws IOException { + GetBigQueryExport.getBigQueryExport(String.format("projects/%s", PROJECT_ID), BQ_EXPORT_ID); + assertThat(stdOut.toString()).contains(BQ_EXPORT_ID); + } + + @Test + public void testListBigQueryExports() throws IOException { + ListBigQueryExports.listBigQueryExports(String.format("projects/%s", PROJECT_ID)); + assertThat(stdOut.toString()).contains(BQ_EXPORT_ID); + } + + @Test + public void testUpdateBigQueryExport() throws IOException { + String filter = "severity=\"MEDIUM\""; + UpdateBigQueryExport.updateBigQueryExport( + String.format("projects/%s", PROJECT_ID), filter, BQ_EXPORT_ID); + assertThat(stdOut.toString()).contains("BigQueryExport updated successfully!"); + } +} diff --git a/security-command-center/snippets/src/test/java/MuteFindingIT.java b/security-command-center/snippets/src/test/java/MuteFindingIT.java new file mode 100644 index 00000000000..9e32834c7f7 --- /dev/null +++ b/security-command-center/snippets/src/test/java/MuteFindingIT.java @@ -0,0 +1,247 @@ +/* + * 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. + */ + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import com.google.cloud.securitycenter.v1.CreateSourceRequest; +import com.google.cloud.securitycenter.v1.Finding; +import com.google.cloud.securitycenter.v1.Finding.FindingClass; +import com.google.cloud.securitycenter.v1.Finding.Mute; +import com.google.cloud.securitycenter.v1.Finding.Severity; +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.SecurityCenterClient; +import com.google.cloud.securitycenter.v1.SecurityCenterClient.ListFindingsPagedResponse; +import com.google.cloud.securitycenter.v1.Source; +import com.google.protobuf.Timestamp; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.time.Instant; +import java.util.UUID; +import muteconfig.BulkMuteFindings; +import muteconfig.CreateMuteRule; +import muteconfig.DeleteMuteRule; +import muteconfig.GetMuteRule; +import muteconfig.ListMuteRules; +import muteconfig.SetMuteFinding; +import muteconfig.SetUnmuteFinding; +import muteconfig.UpdateMuteRule; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +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 MuteFindingIT { + + // TODO(Developer): Replace the below variables. + private static final String PROJECT_ID = System.getenv("SCC_PROJECT_ID"); + private static final String ORGANIZATION_ID = System.getenv("SCC_PROJECT_ORG_ID"); + + private static final String MUTE_RULE_CREATE = "random-mute-id-" + UUID.randomUUID(); + private static final String MUTE_RULE_UPDATE = "random-mute-id-" + UUID.randomUUID(); + private static Source SOURCE; + // The findings will be used to test bulk mute. + private static Finding FINDING_1; + private static Finding FINDING_2; + private static Finding FINDING_3; + + private static ByteArrayOutputStream stdOut; + + // 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 { + 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"); + + // 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); + // Create source. + SOURCE = createSource(ORGANIZATION_ID); + // Create findings within the source. + String uuid = UUID.randomUUID().toString().split("-")[0]; + FINDING_1 = createFinding(SOURCE.getName(), "1testingscc" + uuid); + FINDING_2 = createFinding(SOURCE.getName(), "2testingscc" + uuid); + FINDING_3 = createFinding(SOURCE.getName(), "3testingscc" + uuid); + + stdOut = null; + System.setOut(out); + } + + @AfterClass + 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); + stdOut = null; + System.setOut(out); + } + + public static Source createSource(String organizationId) throws IOException { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + + Source source = + Source.newBuilder() + .setDisplayName("Custom display name") + .setDescription("A source that does X") + .build(); + + CreateSourceRequest createSourceRequest = + CreateSourceRequest.newBuilder() + .setParent(String.format("organizations/%s", organizationId)) + .setSource(source) + .build(); + + Source response = client.createSource(createSourceRequest); + System.out.println("Created source : " + response.getName()); + return response; + } + } + + public static Finding createFinding(String sourceName, String findingId) throws IOException { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + + Instant eventTime = Instant.now(); + + // The resource this finding applies to. The Cloud Security Command Center UI can link + // the findings for a resource to the corresponding asset of a resource + // if there are matches. + // TODO(Developer): Replace the sample resource name + String resourceName = "//cloudresourcemanager.googleapis.com/organizations/11232"; + + // Set up a request to create a finding in a source. + Finding finding = + Finding.newBuilder() + .setParent(sourceName) + .setState(State.ACTIVE) + .setSeverity(Severity.LOW) + .setMute(Mute.UNMUTED) + .setFindingClass(FindingClass.OBSERVATION) + .setResourceName(resourceName) + .setEventTime( + Timestamp.newBuilder() + .setSeconds(eventTime.getEpochSecond()) + .setNanos(eventTime.getNano())) + .setCategory("LOW_RISK_ONE") + .build(); + + Finding response = client.createFinding(sourceName, findingId, finding); + + System.out.println("Created Finding: " + response); + return response; + } + } + + public static ListFindingsPagedResponse getAllFindings(String sourceName) throws IOException { + try (SecurityCenterClient client = SecurityCenterClient.create()) { + + ListFindingsRequest request = ListFindingsRequest.newBuilder().setParent(sourceName).build(); + + return client.listFindings(request); + } + } + + private static String getOrganizationId() { + return "1081635000895"; + } + + private static String getProject() { + return "project-a-id"; + } + + @Before + public void beforeEach() { + stdOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdOut)); + } + + @After + public void afterEach() { + stdOut = null; + System.setOut(null); + } + + @Test + public void testGetMuteRule() { + GetMuteRule.getMuteRule(PROJECT_ID, MUTE_RULE_CREATE); + assertThat(stdOut.toString()).contains("Retrieved the mute config: "); + assertThat(stdOut.toString()).contains(MUTE_RULE_CREATE); + } + + @Test + public void testListMuteRules() { + ListMuteRules.listMuteRules(String.format("projects/%s", PROJECT_ID)); + assertThat(stdOut.toString()).contains(MUTE_RULE_CREATE); + assertThat(stdOut.toString()).contains(MUTE_RULE_UPDATE); + } + + @Test + public void testUpdateMuteRules() { + UpdateMuteRule.updateMuteRule( + String.format("projects/%s/muteConfigs/%s", PROJECT_ID, MUTE_RULE_UPDATE)); + GetMuteRule.getMuteRule(PROJECT_ID, MUTE_RULE_UPDATE); + assertThat(stdOut.toString()).contains("Updated mute config description"); + } + + @Test + public void testSetMuteFinding() { + SetMuteFinding.setMute(FINDING_1.getName()); + assertThat(stdOut.toString()) + .contains("Mute value for the finding " + FINDING_1.getName() + " is: " + "MUTED"); + SetUnmuteFinding.setUnmute(FINDING_1.getName()); + assertThat(stdOut.toString()) + .contains("Mute value for the finding " + FINDING_1.getName() + " is: " + "UNMUTED"); + } + + @Test + public void testBulkMuteFindings() throws IOException { + // Mute findings that belong to this project. + BulkMuteFindings.bulkMute( + String.format("projects/%s", PROJECT_ID), + String.format("resource.project_display_name=\"%s\"", PROJECT_ID)); + + // Get all findings in the source to check if they are muted. + ListFindingsPagedResponse response = + getAllFindings( + String.format("projects/%s/sources/%s", PROJECT_ID, SOURCE.getName().split("/")[3])); + for (ListFindingsResult finding : response.iterateAll()) { + Assert.assertEquals(finding.getFinding().getMute(), Mute.MUTED); + } + } +} diff --git a/speech/README.md b/speech/README.md deleted file mode 100644 index 635274c71cd..00000000000 --- a/speech/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Cloud Speech API samples for Java - -These samples have moved to [googleapis/java-speech](https://github.com/googleapis/java-speech/tree/main/samples). \ No newline at end of file diff --git a/speech/pom.xml b/speech/pom.xml new file mode 100644 index 00000000000..085ac51000c --- /dev/null +++ b/speech/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + com.example.speech + google-cloud-speech-snippets + jar + Google Cloud Speech Snippets + https://github.com/GoogleCloudPlatform/java-docs-samples/tree/main/speech + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + 1.8 + 1.8 + UTF-8 + + + + + + + + com.google.cloud + libraries-bom + 26.1.3 + pom + import + + + + + + + org.json + json + 20220924 + + + com.google.cloud + google-cloud-speech + + + + com.google.cloud + google-cloud-storage + + + commons-cli + commons-cli + 1.5.0 + + + junit + junit + 4.13.2 + test + + + com.google.truth + truth + 1.1.3 + test + + + + + diff --git a/speech/resources/Google_Gnome.wav b/speech/resources/Google_Gnome.wav new file mode 100644 index 00000000000..2f497b7fbe7 Binary files /dev/null and b/speech/resources/Google_Gnome.wav differ diff --git a/speech/resources/audio.raw b/speech/resources/audio.raw new file mode 100644 index 00000000000..5ebf79d3c9c Binary files /dev/null and b/speech/resources/audio.raw differ diff --git a/speech/resources/commercial_mono.wav b/speech/resources/commercial_mono.wav new file mode 100644 index 00000000000..e6b9ed434f9 Binary files /dev/null and b/speech/resources/commercial_mono.wav differ diff --git a/speech/resources/commercial_stereo.wav b/speech/resources/commercial_stereo.wav new file mode 100644 index 00000000000..467f3687702 Binary files /dev/null and b/speech/resources/commercial_stereo.wav differ diff --git a/speech/src/main/java/com/example/speech/InfiniteStreamRecognize.java b/speech/src/main/java/com/example/speech/InfiniteStreamRecognize.java new file mode 100644 index 00000000000..1695f08e63a --- /dev/null +++ b/speech/src/main/java/com/example/speech/InfiniteStreamRecognize.java @@ -0,0 +1,302 @@ +/* + * 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.speech; + +// [START speech_transcribe_infinite_streaming] + +import com.google.api.gax.rpc.ClientStream; +import com.google.api.gax.rpc.ResponseObserver; +import com.google.api.gax.rpc.StreamController; +import com.google.cloud.speech.v1p1beta1.RecognitionConfig; +import com.google.cloud.speech.v1p1beta1.SpeechClient; +import com.google.cloud.speech.v1p1beta1.SpeechRecognitionAlternative; +import com.google.cloud.speech.v1p1beta1.StreamingRecognitionConfig; +import com.google.cloud.speech.v1p1beta1.StreamingRecognitionResult; +import com.google.cloud.speech.v1p1beta1.StreamingRecognizeRequest; +import com.google.cloud.speech.v1p1beta1.StreamingRecognizeResponse; +import com.google.protobuf.ByteString; +import com.google.protobuf.Duration; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.DataLine.Info; +import javax.sound.sampled.TargetDataLine; + +public class InfiniteStreamRecognize { + + private static final int STREAMING_LIMIT = 290000; // ~5 minutes + + public static final String RED = "\033[0;31m"; + public static final String GREEN = "\033[0;32m"; + public static final String YELLOW = "\033[0;33m"; + + // Creating shared object + private static volatile BlockingQueue sharedQueue = new LinkedBlockingQueue(); + private static TargetDataLine targetDataLine; + private static int BYTES_PER_BUFFER = 6400; // buffer size in bytes + + private static int restartCounter = 0; + private static ArrayList audioInput = new ArrayList(); + private static ArrayList lastAudioInput = new ArrayList(); + private static int resultEndTimeInMS = 0; + private static int isFinalEndTime = 0; + private static int finalRequestEndTime = 0; + private static boolean newStream = true; + private static double bridgingOffset = 0; + private static boolean lastTranscriptWasFinal = false; + private static StreamController referenceToStreamController; + private static ByteString tempByteString; + + public static void main(String... args) { + InfiniteStreamRecognizeOptions options = InfiniteStreamRecognizeOptions.fromFlags(args); + if (options == null) { + // Could not parse. + System.out.println("Failed to parse options."); + System.exit(1); + } + + try { + infiniteStreamingRecognize(options.langCode); + } catch (Exception e) { + System.out.println("Exception caught: " + e); + } + } + + public static String convertMillisToDate(double milliSeconds) { + long millis = (long) milliSeconds; + DecimalFormat format = new DecimalFormat(); + format.setMinimumIntegerDigits(2); + return String.format( + "%s:%s /", + format.format(TimeUnit.MILLISECONDS.toMinutes(millis)), + format.format( + TimeUnit.MILLISECONDS.toSeconds(millis) + - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis)))); + } + + /** Performs infinite streaming speech recognition */ + public static void infiniteStreamingRecognize(String languageCode) throws Exception { + + // Microphone Input buffering + class MicBuffer implements Runnable { + + @Override + public void run() { + System.out.println(YELLOW); + System.out.println("Start speaking...Press Ctrl-C to stop"); + targetDataLine.start(); + byte[] data = new byte[BYTES_PER_BUFFER]; + while (targetDataLine.isOpen()) { + try { + int numBytesRead = targetDataLine.read(data, 0, data.length); + if ((numBytesRead <= 0) && (targetDataLine.isOpen())) { + continue; + } + sharedQueue.put(data.clone()); + } catch (InterruptedException e) { + System.out.println("Microphone input buffering interrupted : " + e.getMessage()); + } + } + } + } + + // Creating microphone input buffer thread + MicBuffer micrunnable = new MicBuffer(); + Thread micThread = new Thread(micrunnable); + ResponseObserver responseObserver = null; + try (SpeechClient client = SpeechClient.create()) { + ClientStream clientStream; + responseObserver = + new ResponseObserver() { + + ArrayList responses = new ArrayList<>(); + + public void onStart(StreamController controller) { + referenceToStreamController = controller; + } + + public void onResponse(StreamingRecognizeResponse response) { + responses.add(response); + StreamingRecognitionResult result = response.getResultsList().get(0); + Duration resultEndTime = result.getResultEndTime(); + resultEndTimeInMS = + (int) + ((resultEndTime.getSeconds() * 1000) + (resultEndTime.getNanos() / 1000000)); + double correctedTime = + resultEndTimeInMS - bridgingOffset + (STREAMING_LIMIT * restartCounter); + + SpeechRecognitionAlternative alternative = result.getAlternativesList().get(0); + if (result.getIsFinal()) { + System.out.print(GREEN); + System.out.print("\033[2K\r"); + System.out.printf( + "%s: %s [confidence: %.2f]\n", + convertMillisToDate(correctedTime), + alternative.getTranscript(), + alternative.getConfidence()); + isFinalEndTime = resultEndTimeInMS; + lastTranscriptWasFinal = true; + } else { + System.out.print(RED); + System.out.print("\033[2K\r"); + System.out.printf( + "%s: %s", convertMillisToDate(correctedTime), alternative.getTranscript()); + lastTranscriptWasFinal = false; + } + } + + public void onComplete() {} + + public void onError(Throwable t) {} + }; + clientStream = client.streamingRecognizeCallable().splitCall(responseObserver); + + RecognitionConfig recognitionConfig = + RecognitionConfig.newBuilder() + .setEncoding(RecognitionConfig.AudioEncoding.LINEAR16) + .setLanguageCode(languageCode) + .setSampleRateHertz(16000) + .build(); + + StreamingRecognitionConfig streamingRecognitionConfig = + StreamingRecognitionConfig.newBuilder() + .setConfig(recognitionConfig) + .setInterimResults(true) + .build(); + + StreamingRecognizeRequest request = + StreamingRecognizeRequest.newBuilder() + .setStreamingConfig(streamingRecognitionConfig) + .build(); // The first request in a streaming call has to be a config + + clientStream.send(request); + + try { + // SampleRate:16000Hz, SampleSizeInBits: 16, Number of channels: 1, Signed: true, + // bigEndian: false + AudioFormat audioFormat = new AudioFormat(16000, 16, 1, true, false); + DataLine.Info targetInfo = + new Info( + TargetDataLine.class, + audioFormat); // Set the system information to read from the microphone audio + // stream + + if (!AudioSystem.isLineSupported(targetInfo)) { + System.out.println("Microphone not supported"); + System.exit(0); + } + // Target data line captures the audio stream the microphone produces. + targetDataLine = (TargetDataLine) AudioSystem.getLine(targetInfo); + targetDataLine.open(audioFormat); + micThread.start(); + + long startTime = System.currentTimeMillis(); + + while (true) { + + long estimatedTime = System.currentTimeMillis() - startTime; + + if (estimatedTime >= STREAMING_LIMIT) { + + clientStream.closeSend(); + referenceToStreamController.cancel(); // remove Observer + + if (resultEndTimeInMS > 0) { + finalRequestEndTime = isFinalEndTime; + } + resultEndTimeInMS = 0; + + lastAudioInput = null; + lastAudioInput = audioInput; + audioInput = new ArrayList(); + + restartCounter++; + + if (!lastTranscriptWasFinal) { + System.out.print('\n'); + } + + newStream = true; + + clientStream = client.streamingRecognizeCallable().splitCall(responseObserver); + + request = + StreamingRecognizeRequest.newBuilder() + .setStreamingConfig(streamingRecognitionConfig) + .build(); + + System.out.println(YELLOW); + System.out.printf("%d: RESTARTING REQUEST\n", restartCounter * STREAMING_LIMIT); + + startTime = System.currentTimeMillis(); + + } else { + + if ((newStream) && (lastAudioInput.size() > 0)) { + // if this is the first audio from a new request + // calculate amount of unfinalized audio from last request + // resend the audio to the speech client before incoming audio + double chunkTime = STREAMING_LIMIT / lastAudioInput.size(); + // ms length of each chunk in previous request audio arrayList + if (chunkTime != 0) { + if (bridgingOffset < 0) { + // bridging Offset accounts for time of resent audio + // calculated from last request + bridgingOffset = 0; + } + if (bridgingOffset > finalRequestEndTime) { + bridgingOffset = finalRequestEndTime; + } + int chunksFromMs = + (int) Math.floor((finalRequestEndTime - bridgingOffset) / chunkTime); + // chunks from MS is number of chunks to resend + bridgingOffset = + (int) Math.floor((lastAudioInput.size() - chunksFromMs) * chunkTime); + // set bridging offset for next request + for (int i = chunksFromMs; i < lastAudioInput.size(); i++) { + request = + StreamingRecognizeRequest.newBuilder() + .setAudioContent(lastAudioInput.get(i)) + .build(); + clientStream.send(request); + } + } + newStream = false; + } + + tempByteString = ByteString.copyFrom(sharedQueue.take()); + + request = + StreamingRecognizeRequest.newBuilder().setAudioContent(tempByteString).build(); + + audioInput.add(tempByteString); + } + + clientStream.send(request); + } + } catch (Exception e) { + System.out.println(e); + } + } + } +} +// [END speech_transcribe_infinite_streaming] diff --git a/speech/src/main/java/com/example/speech/InfiniteStreamRecognizeOptions.java b/speech/src/main/java/com/example/speech/InfiniteStreamRecognizeOptions.java new file mode 100644 index 00000000000..909ff2be08c --- /dev/null +++ b/speech/src/main/java/com/example/speech/InfiniteStreamRecognizeOptions.java @@ -0,0 +1,55 @@ +/* + * 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.speech; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; + +public class InfiniteStreamRecognizeOptions { + String langCode = "en-US"; // by default english US + + /** Construct an InfiniteStreamRecognizeOptions class from command line flags. */ + public static InfiniteStreamRecognizeOptions fromFlags(String[] args) { + Options options = new Options(); + options.addOption( + Option.builder() + .type(String.class) + .longOpt("lang_code") + .hasArg() + .desc("Language code") + .build()); + + CommandLineParser parser = new DefaultParser(); + CommandLine commandLine; + try { + commandLine = parser.parse(options, args); + InfiniteStreamRecognizeOptions res = new InfiniteStreamRecognizeOptions(); + + if (commandLine.hasOption("lang_code")) { + res.langCode = commandLine.getOptionValue("lang_code"); + } + return res; + } catch (ParseException e) { + System.err.println(e.getMessage()); + return null; + } + } +} diff --git a/speech/src/main/java/com/example/speech/QuickstartSample.java b/speech/src/main/java/com/example/speech/QuickstartSample.java new file mode 100644 index 00000000000..245d0d0b812 --- /dev/null +++ b/speech/src/main/java/com/example/speech/QuickstartSample.java @@ -0,0 +1,62 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.speech; + +// [START speech_quickstart] +// Imports the Google Cloud client library +import com.google.cloud.speech.v1.RecognitionAudio; +import com.google.cloud.speech.v1.RecognitionConfig; +import com.google.cloud.speech.v1.RecognitionConfig.AudioEncoding; +import com.google.cloud.speech.v1.RecognizeResponse; +import com.google.cloud.speech.v1.SpeechClient; +import com.google.cloud.speech.v1.SpeechRecognitionAlternative; +import com.google.cloud.speech.v1.SpeechRecognitionResult; +import java.util.List; + +public class QuickstartSample { + + /** Demonstrates using the Speech API to transcribe an audio file. */ + public static void main(String... args) throws Exception { + // Instantiates a client + try (SpeechClient speechClient = SpeechClient.create()) { + + // The path to the audio file to transcribe + String gcsUri = "gs://cloud-samples-data/speech/brooklyn_bridge.raw"; + + // Builds the sync recognize request + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.LINEAR16) + .setSampleRateHertz(16000) + .setLanguageCode("en-US") + .build(); + RecognitionAudio audio = RecognitionAudio.newBuilder().setUri(gcsUri).build(); + + // Performs speech recognition on the audio file + RecognizeResponse response = speechClient.recognize(config, audio); + List results = response.getResultsList(); + + for (SpeechRecognitionResult result : results) { + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternativesList().get(0); + System.out.printf("Transcription: %s%n", alternative.getTranscript()); + } + } + } +} +// [END speech_quickstart] diff --git a/speech/src/main/java/com/example/speech/Recognize.java b/speech/src/main/java/com/example/speech/Recognize.java new file mode 100644 index 00000000000..ab060e43ea0 --- /dev/null +++ b/speech/src/main/java/com/example/speech/Recognize.java @@ -0,0 +1,941 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.speech; + +import com.google.api.gax.longrunning.OperationFuture; +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.ApiStreamObserver; +import com.google.api.gax.rpc.BidiStreamingCallable; +import com.google.api.gax.rpc.ClientStream; +import com.google.api.gax.rpc.ResponseObserver; +import com.google.api.gax.rpc.StreamController; +import com.google.cloud.speech.v1.LongRunningRecognizeMetadata; +import com.google.cloud.speech.v1.LongRunningRecognizeResponse; +import com.google.cloud.speech.v1.RecognitionAudio; +import com.google.cloud.speech.v1.RecognitionConfig; +import com.google.cloud.speech.v1.RecognitionConfig.AudioEncoding; +import com.google.cloud.speech.v1.RecognizeResponse; +import com.google.cloud.speech.v1.SpeechClient; +import com.google.cloud.speech.v1.SpeechRecognitionAlternative; +import com.google.cloud.speech.v1.SpeechRecognitionResult; +import com.google.cloud.speech.v1.SpeechSettings; +import com.google.cloud.speech.v1.StreamingRecognitionConfig; +import com.google.cloud.speech.v1.StreamingRecognitionResult; +import com.google.cloud.speech.v1.StreamingRecognizeRequest; +import com.google.cloud.speech.v1.StreamingRecognizeResponse; +import com.google.cloud.speech.v1.WordInfo; +import com.google.common.util.concurrent.SettableFuture; +import com.google.protobuf.ByteString; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.DataLine.Info; +import javax.sound.sampled.TargetDataLine; +import org.threeten.bp.Duration; + +public class Recognize { + + /** Run speech recognition tasks. */ + public static void main(String... args) throws Exception { + if (args.length < 1) { + System.out.println("Usage:"); + System.out.printf( + "\tjava %s \"\" \"\"\n" + + "Commands:\n" + + "\tsyncrecognize | asyncrecognize | streamrecognize | micstreamrecognize \n" + + "\t| wordoffsets | auto-punctuation | stream-punctuation \n" + + "\t| enhanced-model | model-selection | multi-channel\n" + + "Path:\n\tA file path (ex: ./resources/audio.raw) or a URI " + + "for a Cloud Storage resource (gs://...)\n", + Recognize.class.getCanonicalName()); + return; + } + String command = args[0]; + String path = args.length > 1 ? args[1] : ""; + + // Use command and GCS path pattern to invoke transcription. + if (command.equals("syncrecognize")) { + if (path.startsWith("gs://")) { + syncRecognizeGcs(path); + } else { + syncRecognizeFile(path); + } + } else if (command.equals("wordoffsets")) { + if (path.startsWith("gs://")) { + asyncRecognizeWords(path); + } else { + syncRecognizeWords(path); + } + } else if (command.equals("asyncrecognize")) { + if (path.startsWith("gs://")) { + asyncRecognizeGcs(path); + } else { + asyncRecognizeFile(path); + } + } else if (command.equals("streamrecognize")) { + streamingRecognizeFile(path); + } else if (command.equals("micstreamrecognize")) { + streamingMicRecognize(); + } else if (command.equals("auto-punctuation")) { + if (path.startsWith("gs://")) { + transcribeGcsWithAutomaticPunctuation(path); + } else { + transcribeFileWithAutomaticPunctuation(path); + } + } else if (command.equals("stream-punctuation")) { + streamingTranscribeWithAutomaticPunctuation(path); + } else if (command.equals("enhanced-model")) { + transcribeFileWithEnhancedModel(path); + } else if (command.equals("model-selection")) { + if (path.startsWith("gs://")) { + transcribeModelSelectionGcs(path); + } else { + transcribeModelSelection(path); + } + } else if (command.equals("multi-channel")) { + if (path.startsWith("gs://")) { + transcribeMultiChannelGcs(path); + } else { + transcribeMultiChannel(path); + } + } + } + + // [START speech_transcribe_sync] + /** + * Performs speech recognition on raw PCM audio and prints the transcription. + * + * @param fileName the path to a PCM audio file to transcribe. + */ + public static void syncRecognizeFile(String fileName) throws Exception { + try (SpeechClient speech = SpeechClient.create()) { + Path path = Paths.get(fileName); + byte[] data = Files.readAllBytes(path); + ByteString audioBytes = ByteString.copyFrom(data); + + // Configure request with local raw PCM audio + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.LINEAR16) + .setLanguageCode("en-US") + .setSampleRateHertz(16000) + .build(); + RecognitionAudio audio = RecognitionAudio.newBuilder().setContent(audioBytes).build(); + + // Use blocking call to get audio transcript + RecognizeResponse response = speech.recognize(config, audio); + List results = response.getResultsList(); + + for (SpeechRecognitionResult result : results) { + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternativesList().get(0); + System.out.printf("Transcription: %s%n", alternative.getTranscript()); + } + } + } + // [END speech_transcribe_sync] + + /** + * Performs sync recognize and prints word time offsets. + * + * @param fileName the path to a PCM audio file to transcribe get offsets on. + */ + public static void syncRecognizeWords(String fileName) throws Exception { + try (SpeechClient speech = SpeechClient.create()) { + Path path = Paths.get(fileName); + byte[] data = Files.readAllBytes(path); + ByteString audioBytes = ByteString.copyFrom(data); + + // Configure request with local raw PCM audio + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.LINEAR16) + .setLanguageCode("en-US") + .setSampleRateHertz(16000) + .setEnableWordTimeOffsets(true) + .build(); + RecognitionAudio audio = RecognitionAudio.newBuilder().setContent(audioBytes).build(); + + // Use blocking call to get audio transcript + RecognizeResponse response = speech.recognize(config, audio); + List results = response.getResultsList(); + + for (SpeechRecognitionResult result : results) { + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternativesList().get(0); + System.out.printf("Transcription: %s%n", alternative.getTranscript()); + for (WordInfo wordInfo : alternative.getWordsList()) { + System.out.println(wordInfo.getWord()); + System.out.printf( + "\t%s.%s sec - %s.%s sec\n", + wordInfo.getStartTime().getSeconds(), + wordInfo.getStartTime().getNanos() / 100000000, + wordInfo.getEndTime().getSeconds(), + wordInfo.getEndTime().getNanos() / 100000000); + } + } + } + } + + // [START speech_transcribe_sync_gcs] + /** + * Performs speech recognition on remote FLAC file and prints the transcription. + * + * @param gcsUri the path to the remote FLAC audio file to transcribe. + */ + public static void syncRecognizeGcs(String gcsUri) throws Exception { + // Instantiates a client with GOOGLE_APPLICATION_CREDENTIALS + try (SpeechClient speech = SpeechClient.create()) { + // Builds the request for remote FLAC file + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.FLAC) + .setLanguageCode("en-US") + .setSampleRateHertz(16000) + .build(); + RecognitionAudio audio = RecognitionAudio.newBuilder().setUri(gcsUri).build(); + + // Use blocking call for getting audio transcript + RecognizeResponse response = speech.recognize(config, audio); + List results = response.getResultsList(); + + for (SpeechRecognitionResult result : results) { + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternativesList().get(0); + System.out.printf("Transcription: %s%n", alternative.getTranscript()); + } + } + } + // [END speech_transcribe_sync_gcs] + + // [START speech_transcribe_async] + /** + * Performs non-blocking speech recognition on raw PCM audio and prints the transcription. Note + * that transcription is limited to 60 seconds audio. + * + * @param fileName the path to a PCM audio file to transcribe. + */ + public static void asyncRecognizeFile(String fileName) throws Exception { + // Instantiates a client with GOOGLE_APPLICATION_CREDENTIALS + try (SpeechClient speech = SpeechClient.create()) { + + Path path = Paths.get(fileName); + byte[] data = Files.readAllBytes(path); + ByteString audioBytes = ByteString.copyFrom(data); + + // Configure request with local raw PCM audio + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.LINEAR16) + .setLanguageCode("en-US") + .setSampleRateHertz(16000) + .build(); + RecognitionAudio audio = RecognitionAudio.newBuilder().setContent(audioBytes).build(); + + // Use non-blocking call for getting file transcription + OperationFuture response = + speech.longRunningRecognizeAsync(config, audio); + + while (!response.isDone()) { + System.out.println("Waiting for response..."); + Thread.sleep(10000); + } + + List results = response.get().getResultsList(); + + for (SpeechRecognitionResult result : results) { + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternativesList().get(0); + System.out.printf("Transcription: %s%n", alternative.getTranscript()); + } + } + } + // [END speech_transcribe_async] + + // [START speech_transcribe_async_word_time_offsets_gcs] + /** + * Performs non-blocking speech recognition on remote FLAC file and prints the transcription as + * well as word time offsets. + * + * @param gcsUri the path to the remote LINEAR16 audio file to transcribe. + */ + public static void asyncRecognizeWords(String gcsUri) throws Exception { + // Instantiates a client with GOOGLE_APPLICATION_CREDENTIALS + try (SpeechClient speech = SpeechClient.create()) { + + // Configure remote file request for FLAC + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.FLAC) + .setLanguageCode("en-US") + .setSampleRateHertz(16000) + .setEnableWordTimeOffsets(true) + .build(); + RecognitionAudio audio = RecognitionAudio.newBuilder().setUri(gcsUri).build(); + + // Use non-blocking call for getting file transcription + OperationFuture response = + speech.longRunningRecognizeAsync(config, audio); + while (!response.isDone()) { + System.out.println("Waiting for response..."); + Thread.sleep(10000); + } + + List results = response.get().getResultsList(); + + for (SpeechRecognitionResult result : results) { + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternativesList().get(0); + System.out.printf("Transcription: %s\n", alternative.getTranscript()); + for (WordInfo wordInfo : alternative.getWordsList()) { + System.out.println(wordInfo.getWord()); + System.out.printf( + "\t%s.%s sec - %s.%s sec\n", + wordInfo.getStartTime().getSeconds(), + wordInfo.getStartTime().getNanos() / 100000000, + wordInfo.getEndTime().getSeconds(), + wordInfo.getEndTime().getNanos() / 100000000); + } + } + } + } + // [END speech_transcribe_async_word_time_offsets_gcs] + + // [START speech_transcribe_async_gcs] + /** + * Performs non-blocking speech recognition on remote FLAC file and prints the transcription. + * + * @param gcsUri the path to the remote LINEAR16 audio file to transcribe. + */ + public static void asyncRecognizeGcs(String gcsUri) throws Exception { + // Configure polling algorithm + SpeechSettings.Builder speechSettings = SpeechSettings.newBuilder(); + TimedRetryAlgorithm timedRetryAlgorithm = + OperationTimedPollAlgorithm.create( + RetrySettings.newBuilder() + .setInitialRetryDelay(Duration.ofMillis(500L)) + .setRetryDelayMultiplier(1.5) + .setMaxRetryDelay(Duration.ofMillis(5000L)) + .setInitialRpcTimeout(Duration.ZERO) // ignored + .setRpcTimeoutMultiplier(1.0) // ignored + .setMaxRpcTimeout(Duration.ZERO) // ignored + .setTotalTimeout(Duration.ofHours(24L)) // set polling timeout to 24 hours + .build()); + speechSettings.longRunningRecognizeOperationSettings().setPollingAlgorithm(timedRetryAlgorithm); + + // Instantiates a client with GOOGLE_APPLICATION_CREDENTIALS + try (SpeechClient speech = SpeechClient.create(speechSettings.build())) { + + // Configure remote file request for FLAC + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.FLAC) + .setLanguageCode("en-US") + .setSampleRateHertz(16000) + .build(); + RecognitionAudio audio = RecognitionAudio.newBuilder().setUri(gcsUri).build(); + + // Use non-blocking call for getting file transcription + OperationFuture response = + speech.longRunningRecognizeAsync(config, audio); + while (!response.isDone()) { + System.out.println("Waiting for response..."); + Thread.sleep(10000); + } + + List results = response.get().getResultsList(); + + for (SpeechRecognitionResult result : results) { + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternativesList().get(0); + System.out.printf("Transcription: %s\n", alternative.getTranscript()); + } + } + } + // [END speech_transcribe_async_gcs] + + // [START speech_transcribe_streaming] + /** + * Performs streaming speech recognition on raw PCM audio data. + * + * @param fileName the path to a PCM audio file to transcribe. + */ + public static void streamingRecognizeFile(String fileName) throws Exception, IOException { + Path path = Paths.get(fileName); + byte[] data = Files.readAllBytes(path); + + // Instantiates a client with GOOGLE_APPLICATION_CREDENTIALS + try (SpeechClient speech = SpeechClient.create()) { + + // Configure request with local raw PCM audio + RecognitionConfig recConfig = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.LINEAR16) + .setLanguageCode("en-US") + .setSampleRateHertz(16000) + .setModel("default") + .build(); + StreamingRecognitionConfig config = + StreamingRecognitionConfig.newBuilder().setConfig(recConfig).build(); + + class ResponseApiStreamingObserver implements ApiStreamObserver { + private final SettableFuture> future = SettableFuture.create(); + private final List messages = new java.util.ArrayList(); + + @Override + public void onNext(T message) { + messages.add(message); + } + + @Override + public void onError(Throwable t) { + future.setException(t); + } + + @Override + public void onCompleted() { + future.set(messages); + } + + // Returns the SettableFuture object to get received messages / exceptions. + public SettableFuture> future() { + return future; + } + } + + ResponseApiStreamingObserver responseObserver = + new ResponseApiStreamingObserver<>(); + + BidiStreamingCallable callable = + speech.streamingRecognizeCallable(); + + ApiStreamObserver requestObserver = + callable.bidiStreamingCall(responseObserver); + + // The first request must **only** contain the audio configuration: + requestObserver.onNext( + StreamingRecognizeRequest.newBuilder().setStreamingConfig(config).build()); + + // Subsequent requests must **only** contain the audio data. + requestObserver.onNext( + StreamingRecognizeRequest.newBuilder() + .setAudioContent(ByteString.copyFrom(data)) + .build()); + + // Mark transmission as completed after sending the data. + requestObserver.onCompleted(); + + List responses = responseObserver.future().get(); + + for (StreamingRecognizeResponse response : responses) { + // For streaming recognize, the results list has one is_final result (if available) followed + // by a number of in-progress results (if iterim_results is true) for subsequent utterances. + // Just print the first result here. + StreamingRecognitionResult result = response.getResultsList().get(0); + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternativesList().get(0); + System.out.printf("Transcript : %s\n", alternative.getTranscript()); + } + } + } + // [END speech_transcribe_streaming] + + // [START speech_sync_recognize_punctuation] + /** + * Performs transcription with automatic punctuation on raw PCM audio data. + * + * @param fileName the path to a PCM audio file to transcribe. + */ + public static void transcribeFileWithAutomaticPunctuation(String fileName) throws Exception { + Path path = Paths.get(fileName); + byte[] content = Files.readAllBytes(path); + + try (SpeechClient speechClient = SpeechClient.create()) { + // Configure request with local raw PCM audio + RecognitionConfig recConfig = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.LINEAR16) + .setLanguageCode("en-US") + .setSampleRateHertz(16000) + .setEnableAutomaticPunctuation(true) + .build(); + + // Get the contents of the local audio file + RecognitionAudio recognitionAudio = + RecognitionAudio.newBuilder().setContent(ByteString.copyFrom(content)).build(); + + // Perform the transcription request + RecognizeResponse recognizeResponse = speechClient.recognize(recConfig, recognitionAudio); + + // Just print the first result here. + SpeechRecognitionResult result = recognizeResponse.getResultsList().get(0); + + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternativesList().get(0); + + // Print out the result + System.out.printf("Transcript : %s\n", alternative.getTranscript()); + } + } + // [END speech_sync_recognize_punctuation] + + // [START speech_transcribe_auto_punctuation] + /** + * Performs transcription on remote FLAC file and prints the transcription. + * + * @param gcsUri the path to the remote FLAC audio file to transcribe. + */ + public static void transcribeGcsWithAutomaticPunctuation(String gcsUri) throws Exception { + try (SpeechClient speechClient = SpeechClient.create()) { + // Configure request with raw PCM audio + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.FLAC) + .setLanguageCode("en-US") + .setSampleRateHertz(16000) + .setEnableAutomaticPunctuation(true) + .build(); + + // Set the remote path for the audio file + RecognitionAudio audio = RecognitionAudio.newBuilder().setUri(gcsUri).build(); + + // Use non-blocking call for getting file transcription + OperationFuture response = + speechClient.longRunningRecognizeAsync(config, audio); + + while (!response.isDone()) { + System.out.println("Waiting for response..."); + Thread.sleep(10000); + } + + // Just print the first result here. + SpeechRecognitionResult result = response.get().getResultsList().get(0); + + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternativesList().get(0); + + // Print out the result + System.out.printf("Transcript : %s\n", alternative.getTranscript()); + } + } + // [END speech_transcribe_auto_punctuation] + + // [START speech_stream_recognize_punctuation] + /** + * Performs streaming speech recognition on raw PCM audio data. + * + * @param fileName the path to a PCM audio file to transcribe. + */ + public static void streamingTranscribeWithAutomaticPunctuation(String fileName) throws Exception { + Path path = Paths.get(fileName); + byte[] data = Files.readAllBytes(path); + + // Instantiates a client with GOOGLE_APPLICATION_CREDENTIALS + try (SpeechClient speech = SpeechClient.create()) { + + // Configure request with local raw PCM audio + RecognitionConfig recConfig = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.LINEAR16) + .setLanguageCode("en-US") + .setSampleRateHertz(16000) + .setEnableAutomaticPunctuation(true) + .build(); + + // Build the streaming config with the audio config + StreamingRecognitionConfig config = + StreamingRecognitionConfig.newBuilder().setConfig(recConfig).build(); + + class ResponseApiStreamingObserver implements ApiStreamObserver { + private final SettableFuture> future = SettableFuture.create(); + private final List messages = new java.util.ArrayList(); + + @Override + public void onNext(T message) { + messages.add(message); + } + + @Override + public void onError(Throwable t) { + future.setException(t); + } + + @Override + public void onCompleted() { + future.set(messages); + } + + // Returns the SettableFuture object to get received messages / exceptions. + public SettableFuture> future() { + return future; + } + } + + ResponseApiStreamingObserver responseObserver = + new ResponseApiStreamingObserver<>(); + + BidiStreamingCallable callable = + speech.streamingRecognizeCallable(); + + ApiStreamObserver requestObserver = + callable.bidiStreamingCall(responseObserver); + + // The first request must **only** contain the audio configuration: + requestObserver.onNext( + StreamingRecognizeRequest.newBuilder().setStreamingConfig(config).build()); + + // Subsequent requests must **only** contain the audio data. + requestObserver.onNext( + StreamingRecognizeRequest.newBuilder() + .setAudioContent(ByteString.copyFrom(data)) + .build()); + + // Mark transmission as completed after sending the data. + requestObserver.onCompleted(); + + List responses = responseObserver.future().get(); + + for (StreamingRecognizeResponse response : responses) { + // For streaming recognize, the results list has one is_final result (if available) followed + // by a number of in-progress results (if iterim_results is true) for subsequent utterances. + // Just print the first result here. + StreamingRecognitionResult result = response.getResultsList().get(0); + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternativesList().get(0); + System.out.printf("Transcript : %s\n", alternative.getTranscript()); + } + } + } + // [END speech_stream_recognize_punctuation] + + // [START speech_transcribe_streaming_mic] + /** Performs microphone streaming speech recognition with a duration of 1 minute. */ + public static void streamingMicRecognize() throws Exception { + + ResponseObserver responseObserver = null; + try (SpeechClient client = SpeechClient.create()) { + + responseObserver = + new ResponseObserver() { + ArrayList responses = new ArrayList<>(); + + public void onStart(StreamController controller) {} + + public void onResponse(StreamingRecognizeResponse response) { + responses.add(response); + } + + public void onComplete() { + for (StreamingRecognizeResponse response : responses) { + StreamingRecognitionResult result = response.getResultsList().get(0); + SpeechRecognitionAlternative alternative = result.getAlternativesList().get(0); + System.out.printf("Transcript : %s\n", alternative.getTranscript()); + } + } + + public void onError(Throwable t) { + System.out.println(t); + } + }; + + ClientStream clientStream = + client.streamingRecognizeCallable().splitCall(responseObserver); + + RecognitionConfig recognitionConfig = + RecognitionConfig.newBuilder() + .setEncoding(RecognitionConfig.AudioEncoding.LINEAR16) + .setLanguageCode("en-US") + .setSampleRateHertz(16000) + .build(); + StreamingRecognitionConfig streamingRecognitionConfig = + StreamingRecognitionConfig.newBuilder().setConfig(recognitionConfig).build(); + + StreamingRecognizeRequest request = + StreamingRecognizeRequest.newBuilder() + .setStreamingConfig(streamingRecognitionConfig) + .build(); // The first request in a streaming call has to be a config + + clientStream.send(request); + // SampleRate:16000Hz, SampleSizeInBits: 16, Number of channels: 1, Signed: true, + // bigEndian: false + AudioFormat audioFormat = new AudioFormat(16000, 16, 1, true, false); + DataLine.Info targetInfo = + new Info( + TargetDataLine.class, + audioFormat); // Set the system information to read from the microphone audio stream + + if (!AudioSystem.isLineSupported(targetInfo)) { + System.out.println("Microphone not supported"); + System.exit(0); + } + // Target data line captures the audio stream the microphone produces. + TargetDataLine targetDataLine = (TargetDataLine) AudioSystem.getLine(targetInfo); + targetDataLine.open(audioFormat); + targetDataLine.start(); + System.out.println("Start speaking"); + long startTime = System.currentTimeMillis(); + // Audio Input Stream + AudioInputStream audio = new AudioInputStream(targetDataLine); + while (true) { + long estimatedTime = System.currentTimeMillis() - startTime; + byte[] data = new byte[6400]; + audio.read(data); + if (estimatedTime > 60000) { // 60 seconds + System.out.println("Stop speaking."); + targetDataLine.stop(); + targetDataLine.close(); + break; + } + request = + StreamingRecognizeRequest.newBuilder() + .setAudioContent(ByteString.copyFrom(data)) + .build(); + clientStream.send(request); + } + } catch (Exception e) { + System.out.println(e); + } + responseObserver.onComplete(); + } + // [END speech_transcribe_streaming_mic] + + // [START speech_transcribe_enhanced_model] + /** + * Transcribe the given audio file using an enhanced model. + * + * @param fileName the path to an audio file. + */ + public static void transcribeFileWithEnhancedModel(String fileName) throws Exception { + Path path = Paths.get(fileName); + byte[] content = Files.readAllBytes(path); + + try (SpeechClient speechClient = SpeechClient.create()) { + // Get the contents of the local audio file + RecognitionAudio recognitionAudio = + RecognitionAudio.newBuilder().setContent(ByteString.copyFrom(content)).build(); + + // Configure request to enable enhanced models + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.LINEAR16) + .setLanguageCode("en-US") + .setSampleRateHertz(8000) + .setUseEnhanced(true) + // A model must be specified to use enhanced model. + .setModel("phone_call") + .build(); + + // Perform the transcription request + RecognizeResponse recognizeResponse = speechClient.recognize(config, recognitionAudio); + + // Print out the results + for (SpeechRecognitionResult result : recognizeResponse.getResultsList()) { + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternatives(0); + System.out.format("Transcript: %s\n\n", alternative.getTranscript()); + } + } + } + // [END speech_transcribe_enhanced_model] + + // [START speech_transcribe_model_selection] + /** + * Performs transcription of the given audio file synchronously with the selected model. + * + * @param fileName the path to a audio file to transcribe + */ + public static void transcribeModelSelection(String fileName) throws Exception { + Path path = Paths.get(fileName); + byte[] content = Files.readAllBytes(path); + + try (SpeechClient speech = SpeechClient.create()) { + // Configure request with video media type + RecognitionConfig recConfig = + RecognitionConfig.newBuilder() + // encoding may either be omitted or must match the value in the file header + .setEncoding(AudioEncoding.LINEAR16) + .setLanguageCode("en-US") + // sample rate hertz may be either be omitted or must match the value in the file + // header + .setSampleRateHertz(16000) + .setModel("video") + .build(); + + RecognitionAudio recognitionAudio = + RecognitionAudio.newBuilder().setContent(ByteString.copyFrom(content)).build(); + + RecognizeResponse recognizeResponse = speech.recognize(recConfig, recognitionAudio); + // Just print the first result here. + SpeechRecognitionResult result = recognizeResponse.getResultsList().get(0); + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternativesList().get(0); + System.out.printf("Transcript : %s\n", alternative.getTranscript()); + } + } + // [END speech_transcribe_model_selection] + + // [START speech_transcribe_model_selection_gcs] + /** + * Performs transcription of the remote audio file asynchronously with the selected model. + * + * @param gcsUri the path to the remote audio file to transcribe. + */ + public static void transcribeModelSelectionGcs(String gcsUri) throws Exception { + try (SpeechClient speech = SpeechClient.create()) { + + // Configure request with video media type + RecognitionConfig config = + RecognitionConfig.newBuilder() + // encoding may either be omitted or must match the value in the file header + .setEncoding(AudioEncoding.LINEAR16) + .setLanguageCode("en-US") + // sample rate hertz may be either be omitted or must match the value in the file + // header + .setSampleRateHertz(16000) + .setModel("video") + .build(); + + RecognitionAudio audio = RecognitionAudio.newBuilder().setUri(gcsUri).build(); + + // Use non-blocking call for getting file transcription + OperationFuture response = + speech.longRunningRecognizeAsync(config, audio); + + while (!response.isDone()) { + System.out.println("Waiting for response..."); + Thread.sleep(10000); + } + + List results = response.get().getResultsList(); + + // Just print the first result here. + SpeechRecognitionResult result = results.get(0); + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternativesList().get(0); + System.out.printf("Transcript : %s\n", alternative.getTranscript()); + } + } + // [END speech_transcribe_model_selection_gcs] + + // [START speech_transcribe_multichannel] + /** + * Transcribe a local audio file with multi-channel recognition + * + * @param fileName the path to local audio file + */ + public static void transcribeMultiChannel(String fileName) throws Exception { + Path path = Paths.get(fileName); + byte[] content = Files.readAllBytes(path); + + try (SpeechClient speechClient = SpeechClient.create()) { + // Get the contents of the local audio file + RecognitionAudio recognitionAudio = + RecognitionAudio.newBuilder().setContent(ByteString.copyFrom(content)).build(); + + // Configure request to enable multiple channels + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.LINEAR16) + .setLanguageCode("en-US") + .setSampleRateHertz(44100) + .setAudioChannelCount(2) + .setEnableSeparateRecognitionPerChannel(true) + .build(); + + // Perform the transcription request + RecognizeResponse recognizeResponse = speechClient.recognize(config, recognitionAudio); + + // Print out the results + for (SpeechRecognitionResult result : recognizeResponse.getResultsList()) { + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternatives(0); + System.out.format("Transcript : %s\n", alternative.getTranscript()); + System.out.printf("Channel Tag : %s\n", result.getChannelTag()); + } + } + } + // [END speech_transcribe_multichannel] + + // [START speech_transcribe_multichannel_gcs] + /** + * Transcribe a remote audio file with multi-channel recognition + * + * @param gcsUri the path to the audio file + */ + public static void transcribeMultiChannelGcs(String gcsUri) throws Exception { + + try (SpeechClient speechClient = SpeechClient.create()) { + + // Configure request to enable multiple channels + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.LINEAR16) + .setLanguageCode("en-US") + .setSampleRateHertz(44100) + .setAudioChannelCount(2) + .setEnableSeparateRecognitionPerChannel(true) + .build(); + + // Set the remote path for the audio file + RecognitionAudio audio = RecognitionAudio.newBuilder().setUri(gcsUri).build(); + + // Use non-blocking call for getting file transcription + OperationFuture response = + speechClient.longRunningRecognizeAsync(config, audio); + + while (!response.isDone()) { + System.out.println("Waiting for response..."); + Thread.sleep(10000); + } + // Just print the first result here. + for (SpeechRecognitionResult result : response.get().getResultsList()) { + + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternativesList().get(0); + + // Print out the result + System.out.printf("Transcript : %s\n", alternative.getTranscript()); + System.out.printf("Channel Tag : %s\n", result.getChannelTag()); + } + } + } + // [END speech_transcribe_multichannel_gcs] +} diff --git a/speech/src/main/java/com/example/speech/RecognizeBeta.java b/speech/src/main/java/com/example/speech/RecognizeBeta.java new file mode 100644 index 00000000000..99544b79eff --- /dev/null +++ b/speech/src/main/java/com/example/speech/RecognizeBeta.java @@ -0,0 +1,532 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.speech; + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.speech.v1p1beta1.LongRunningRecognizeMetadata; +import com.google.cloud.speech.v1p1beta1.LongRunningRecognizeResponse; +import com.google.cloud.speech.v1p1beta1.RecognitionAudio; +import com.google.cloud.speech.v1p1beta1.RecognitionConfig; +import com.google.cloud.speech.v1p1beta1.RecognitionConfig.AudioEncoding; +import com.google.cloud.speech.v1p1beta1.RecognitionMetadata; +import com.google.cloud.speech.v1p1beta1.RecognitionMetadata.InteractionType; +import com.google.cloud.speech.v1p1beta1.RecognitionMetadata.MicrophoneDistance; +import com.google.cloud.speech.v1p1beta1.RecognitionMetadata.RecordingDeviceType; +import com.google.cloud.speech.v1p1beta1.RecognizeResponse; +import com.google.cloud.speech.v1p1beta1.SpeakerDiarizationConfig; +import com.google.cloud.speech.v1p1beta1.SpeechClient; +import com.google.cloud.speech.v1p1beta1.SpeechRecognitionAlternative; +import com.google.cloud.speech.v1p1beta1.SpeechRecognitionResult; +import com.google.cloud.speech.v1p1beta1.WordInfo; +import com.google.protobuf.ByteString; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; + +public class RecognizeBeta { + + /** Run speech recognition tasks. */ + public static void main(String... args) throws Exception { + if (args.length < 1) { + System.out.println("Usage:"); + System.out.printf( + "\tjava %s \"\" \"\"\n" + + "Commands:\n" + + "\t metadata | diarization | multi-channel |\n" + + "\t multi-language | word-level-conf\n" + + "Path:\n\tA file path (ex: ./resources/audio.raw) or a URI " + + "for a Cloud Storage resource (gs://...)\n", + RecognizeBeta.class.getCanonicalName()); + return; + } + String command = args[0]; + String path = args.length > 1 ? args[1] : ""; + + // Use command and GCS path pattern to invoke transcription. + if (command.equals("metadata")) { + transcribeFileWithMetadata(path); + } else if (command.equals("diarization")) { + if (path.startsWith("gs://")) { + transcribeDiarizationGcs(path); + } else { + transcribeDiarization(path); + } + } else if (command.equals("multi-channel")) { + if (path.startsWith("gs://")) { + transcribeMultiChannelGcs(path); + } else { + transcribeMultiChannel(path); + } + } else if (command.equals("multi-language")) { + if (path.startsWith("gs://")) { + transcribeMultiLanguageGcs(path); + } else { + transcribeMultiLanguage(path); + } + } else if (command.equals("word-level-conf")) { + if (path.startsWith("gs://")) { + transcribeWordLevelConfidenceGcs(path); + } else { + transcribeWordLevelConfidence(path); + } + } + } + + // [START speech_transcribe_recognition_metadata_beta] + /** + * Transcribe the given audio file and include recognition metadata in the request. + * + * @param fileName the path to an audio file. + */ + public static void transcribeFileWithMetadata(String fileName) throws Exception { + Path path = Paths.get(fileName); + byte[] content = Files.readAllBytes(path); + + try (SpeechClient speechClient = SpeechClient.create()) { + // Get the contents of the local audio file + RecognitionAudio recognitionAudio = + RecognitionAudio.newBuilder().setContent(ByteString.copyFrom(content)).build(); + + // Construct a recognition metadata object. + // Most metadata fields are specified as enums that can be found + // in speech.enums.RecognitionMetadata + RecognitionMetadata metadata = + RecognitionMetadata.newBuilder() + .setInteractionType(InteractionType.DISCUSSION) + .setMicrophoneDistance(MicrophoneDistance.NEARFIELD) + .setRecordingDeviceType(RecordingDeviceType.SMARTPHONE) + .setRecordingDeviceName("Pixel 2 XL") // Some metadata fields are free form strings + // And some are integers, for instance the 6 digit NAICS code + // https://www.naics.com/search/ + .setIndustryNaicsCodeOfAudio(519190) + .build(); + + // Configure request to enable enhanced models + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.LINEAR16) + .setLanguageCode("en-US") + .setSampleRateHertz(8000) + .setMetadata(metadata) // Add the metadata to the config + .build(); + + // Perform the transcription request + RecognizeResponse recognizeResponse = speechClient.recognize(config, recognitionAudio); + + // Print out the results + for (SpeechRecognitionResult result : recognizeResponse.getResultsList()) { + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternatives(0); + System.out.format("Transcript: %s\n\n", alternative.getTranscript()); + } + } + } + // [END speech_transcribe_recognition_metadata_beta] + + // [START speech_transcribe_diarization_beta] + /** + * Transcribe the given audio file using speaker diarization. + * + * @param fileName the path to an audio file. + */ + public static void transcribeDiarization(String fileName) throws Exception { + Path path = Paths.get(fileName); + byte[] content = Files.readAllBytes(path); + + try (SpeechClient speechClient = SpeechClient.create()) { + // Get the contents of the local audio file + RecognitionAudio recognitionAudio = + RecognitionAudio.newBuilder().setContent(ByteString.copyFrom(content)).build(); + + SpeakerDiarizationConfig speakerDiarizationConfig = + SpeakerDiarizationConfig.newBuilder() + .setEnableSpeakerDiarization(true) + .setMinSpeakerCount(2) + .setMaxSpeakerCount(2) + .build(); + + // Configure request to enable Speaker diarization + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.LINEAR16) + .setLanguageCode("en-US") + .setSampleRateHertz(8000) + .setDiarizationConfig(speakerDiarizationConfig) + .build(); + + // Perform the transcription request + RecognizeResponse recognizeResponse = speechClient.recognize(config, recognitionAudio); + + // Speaker Tags are only included in the last result object, which has only one alternative. + SpeechRecognitionAlternative alternative = + recognizeResponse.getResults(recognizeResponse.getResultsCount() - 1).getAlternatives(0); + + // The alternative is made up of WordInfo objects that contain the speaker_tag. + WordInfo wordInfo = alternative.getWords(0); + int currentSpeakerTag = wordInfo.getSpeakerTag(); + + // For each word, get all the words associated with one speaker, once the speaker changes, + // add a new line with the new speaker and their spoken words. + StringBuilder speakerWords = + new StringBuilder( + String.format("Speaker %d: %s", wordInfo.getSpeakerTag(), wordInfo.getWord())); + + for (int i = 1; i < alternative.getWordsCount(); i++) { + wordInfo = alternative.getWords(i); + if (currentSpeakerTag == wordInfo.getSpeakerTag()) { + speakerWords.append(" "); + speakerWords.append(wordInfo.getWord()); + } else { + speakerWords.append( + String.format("\nSpeaker %d: %s", wordInfo.getSpeakerTag(), wordInfo.getWord())); + currentSpeakerTag = wordInfo.getSpeakerTag(); + } + } + + System.out.println(speakerWords.toString()); + } + } + // [END speech_transcribe_diarization_beta] + + // [START speech_transcribe_diarization_gcs_beta] + /** + * Transcribe a remote audio file using speaker diarization. + * + * @param gcsUri the path to an audio file. + */ + public static void transcribeDiarizationGcs(String gcsUri) throws Exception { + try (SpeechClient speechClient = SpeechClient.create()) { + SpeakerDiarizationConfig speakerDiarizationConfig = + SpeakerDiarizationConfig.newBuilder() + .setEnableSpeakerDiarization(true) + .setMinSpeakerCount(2) + .setMaxSpeakerCount(2) + .build(); + + // Configure request to enable Speaker diarization + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.LINEAR16) + .setLanguageCode("en-US") + .setSampleRateHertz(8000) + .setDiarizationConfig(speakerDiarizationConfig) + .build(); + + // Set the remote path for the audio file + RecognitionAudio audio = RecognitionAudio.newBuilder().setUri(gcsUri).build(); + + // Use non-blocking call for getting file transcription + OperationFuture response = + speechClient.longRunningRecognizeAsync(config, audio); + + while (!response.isDone()) { + System.out.println("Waiting for response..."); + Thread.sleep(10000); + } + + // Speaker Tags are only included in the last result object, which has only one alternative. + LongRunningRecognizeResponse longRunningRecognizeResponse = response.get(); + SpeechRecognitionAlternative alternative = + longRunningRecognizeResponse + .getResults(longRunningRecognizeResponse.getResultsCount() - 1) + .getAlternatives(0); + + // The alternative is made up of WordInfo objects that contain the speaker_tag. + WordInfo wordInfo = alternative.getWords(0); + int currentSpeakerTag = wordInfo.getSpeakerTag(); + + // For each word, get all the words associated with one speaker, once the speaker changes, + // add a new line with the new speaker and their spoken words. + StringBuilder speakerWords = + new StringBuilder( + String.format("Speaker %d: %s", wordInfo.getSpeakerTag(), wordInfo.getWord())); + + for (int i = 1; i < alternative.getWordsCount(); i++) { + wordInfo = alternative.getWords(i); + if (currentSpeakerTag == wordInfo.getSpeakerTag()) { + speakerWords.append(" "); + speakerWords.append(wordInfo.getWord()); + } else { + speakerWords.append( + String.format("\nSpeaker %d: %s", wordInfo.getSpeakerTag(), wordInfo.getWord())); + currentSpeakerTag = wordInfo.getSpeakerTag(); + } + } + + System.out.println(speakerWords.toString()); + } + } + // [END speech_transcribe_diarization_gcs_beta] + + // [START speech_transcribe_multichannel_beta] + /** + * Transcribe a local audio file with multi-channel recognition + * + * @param fileName the path to local audio file + */ + public static void transcribeMultiChannel(String fileName) throws Exception { + Path path = Paths.get(fileName); + byte[] content = Files.readAllBytes(path); + + try (SpeechClient speechClient = SpeechClient.create()) { + // Get the contents of the local audio file + RecognitionAudio recognitionAudio = + RecognitionAudio.newBuilder().setContent(ByteString.copyFrom(content)).build(); + + // Configure request to enable multiple channels + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.LINEAR16) + .setLanguageCode("en-US") + .setSampleRateHertz(44100) + .setAudioChannelCount(2) + .setEnableSeparateRecognitionPerChannel(true) + .build(); + + // Perform the transcription request + RecognizeResponse recognizeResponse = speechClient.recognize(config, recognitionAudio); + + // Print out the results + for (SpeechRecognitionResult result : recognizeResponse.getResultsList()) { + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternatives(0); + System.out.format("Transcript : %s\n", alternative.getTranscript()); + System.out.printf("Channel Tag : %s\n\n", result.getChannelTag()); + } + } + } + // [END speech_transcribe_multichannel_beta] + + // [START speech_transcribe_multichannel_gcs_beta] + /** + * Transcribe a remote audio file with multi-channel recognition + * + * @param gcsUri the path to the audio file + */ + public static void transcribeMultiChannelGcs(String gcsUri) throws Exception { + + try (SpeechClient speechClient = SpeechClient.create()) { + + // Configure request to enable multiple channels + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.LINEAR16) + .setLanguageCode("en-US") + .setSampleRateHertz(44100) + .setAudioChannelCount(2) + .setEnableSeparateRecognitionPerChannel(true) + .build(); + + // Set the remote path for the audio file + RecognitionAudio audio = RecognitionAudio.newBuilder().setUri(gcsUri).build(); + + // Use non-blocking call for getting file transcription + OperationFuture response = + speechClient.longRunningRecognizeAsync(config, audio); + + while (!response.isDone()) { + System.out.println("Waiting for response..."); + Thread.sleep(10000); + } + // Just print the first result here. + for (SpeechRecognitionResult result : response.get().getResultsList()) { + + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternativesList().get(0); + + // Print out the result + System.out.printf("Transcript : %s\n", alternative.getTranscript()); + System.out.printf("Channel Tag : %s\n\n", result.getChannelTag()); + } + } + } + // [END speech_transcribe_multichannel_gcs_beta] + + // [START speech_transcribe_multilanguage_beta] + /** + * Transcribe a local audio file with multi-language recognition + * + * @param fileName the path to the audio file + */ + public static void transcribeMultiLanguage(String fileName) throws Exception { + Path path = Paths.get(fileName); + // Get the contents of the local audio file + byte[] content = Files.readAllBytes(path); + + try (SpeechClient speechClient = SpeechClient.create()) { + + RecognitionAudio recognitionAudio = + RecognitionAudio.newBuilder().setContent(ByteString.copyFrom(content)).build(); + ArrayList languageList = new ArrayList<>(); + languageList.add("es-ES"); + languageList.add("en-US"); + + // Configure request to enable multiple languages + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.LINEAR16) + .setSampleRateHertz(16000) + .setLanguageCode("ja-JP") + .addAllAlternativeLanguageCodes(languageList) + .build(); + // Perform the transcription request + RecognizeResponse recognizeResponse = speechClient.recognize(config, recognitionAudio); + + // Print out the results + for (SpeechRecognitionResult result : recognizeResponse.getResultsList()) { + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternatives(0); + System.out.format("Transcript : %s\n\n", alternative.getTranscript()); + } + } + } + // [END speech_transcribe_multilanguage_beta] + + // [START speech_transcribe_multilanguage_gcs_beta] + /** + * Transcribe a remote audio file with multi-language recognition + * + * @param gcsUri the path to the remote audio file + */ + public static void transcribeMultiLanguageGcs(String gcsUri) throws Exception { + try (SpeechClient speechClient = SpeechClient.create()) { + + ArrayList languageList = new ArrayList<>(); + languageList.add("es-ES"); + languageList.add("en-US"); + + // Configure request to enable multiple languages + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.LINEAR16) + .setSampleRateHertz(16000) + .setLanguageCode("ja-JP") + .addAllAlternativeLanguageCodes(languageList) + .build(); + + // Set the remote path for the audio file + RecognitionAudio audio = RecognitionAudio.newBuilder().setUri(gcsUri).build(); + + // Use non-blocking call for getting file transcription + OperationFuture response = + speechClient.longRunningRecognizeAsync(config, audio); + + while (!response.isDone()) { + System.out.println("Waiting for response..."); + Thread.sleep(10000); + } + + for (SpeechRecognitionResult result : response.get().getResultsList()) { + + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternativesList().get(0); + + // Print out the result + System.out.printf("Transcript : %s\n\n", alternative.getTranscript()); + } + } + } + // [END speech_transcribe_multilanguage_gcs_beta] + + // [START speech_transcribe_word_level_confidence_beta] + /** + * Transcribe a local audio file with word level confidence + * + * @param fileName the path to the local audio file + */ + public static void transcribeWordLevelConfidence(String fileName) throws Exception { + Path path = Paths.get(fileName); + byte[] content = Files.readAllBytes(path); + + try (SpeechClient speechClient = SpeechClient.create()) { + RecognitionAudio recognitionAudio = + RecognitionAudio.newBuilder().setContent(ByteString.copyFrom(content)).build(); + // Configure request to enable word level confidence + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.LINEAR16) + .setSampleRateHertz(16000) + .setLanguageCode("en-US") + .setEnableWordConfidence(true) + .build(); + // Perform the transcription request + RecognizeResponse recognizeResponse = speechClient.recognize(config, recognitionAudio); + + // Print out the results + for (SpeechRecognitionResult result : recognizeResponse.getResultsList()) { + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternatives(0); + System.out.format("Transcript : %s\n", alternative.getTranscript()); + System.out.format( + "First Word and Confidence : %s %s \n", + alternative.getWords(0).getWord(), alternative.getWords(0).getConfidence()); + } + } + } + // [END speech_transcribe_word_level_confidence_beta] + + // [START speech_transcribe_word_level_confidence_gcs_beta] + /** + * Transcribe a remote audio file with word level confidence + * + * @param gcsUri path to the remote audio file + */ + public static void transcribeWordLevelConfidenceGcs(String gcsUri) throws Exception { + try (SpeechClient speechClient = SpeechClient.create()) { + + // Configure request to enable word level confidence + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.FLAC) + .setSampleRateHertz(44100) + .setLanguageCode("en-US") + .setEnableWordConfidence(true) + .build(); + + // Set the remote path for the audio file + RecognitionAudio audio = RecognitionAudio.newBuilder().setUri(gcsUri).build(); + + // Use non-blocking call for getting file transcription + OperationFuture response = + speechClient.longRunningRecognizeAsync(config, audio); + + while (!response.isDone()) { + System.out.println("Waiting for response..."); + Thread.sleep(10000); + } + // Just print the first result here. + SpeechRecognitionResult result = response.get().getResultsList().get(0); + + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternativesList().get(0); + // Print out the result + System.out.printf("Transcript : %s\n", alternative.getTranscript()); + System.out.format( + "First Word and Confidence : %s %s \n", + alternative.getWords(0).getWord(), alternative.getWords(0).getConfidence()); + } + } + // [END speech_transcribe_word_level_confidence_gcs_beta] +} diff --git a/speech/src/main/java/com/example/speech/SpeechAdaptation.java b/speech/src/main/java/com/example/speech/SpeechAdaptation.java new file mode 100644 index 00000000000..4c51672d134 --- /dev/null +++ b/speech/src/main/java/com/example/speech/SpeechAdaptation.java @@ -0,0 +1,73 @@ +/* + * 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.speech; + +// [START speech_adaptation_beta] +import com.google.cloud.speech.v1p1beta1.RecognitionAudio; +import com.google.cloud.speech.v1p1beta1.RecognitionConfig; +import com.google.cloud.speech.v1p1beta1.RecognizeRequest; +import com.google.cloud.speech.v1p1beta1.RecognizeResponse; +import com.google.cloud.speech.v1p1beta1.SpeechClient; +import com.google.cloud.speech.v1p1beta1.SpeechContext; +import com.google.cloud.speech.v1p1beta1.SpeechRecognitionAlternative; +import com.google.cloud.speech.v1p1beta1.SpeechRecognitionResult; +import java.io.IOException; + +public class SpeechAdaptation { + + public void speechAdaptation() throws IOException { + String uriPath = "gs://cloud-samples-data/speech/brooklyn_bridge.mp3"; + speechAdaptation(uriPath); + } + + public static void speechAdaptation(String uriPath) 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 (SpeechClient speechClient = SpeechClient.create()) { + + // Provides "hints" to the speech recognizer to favor specific words and phrases in the + // results. + // https://cloud.google.com/speech-to-text/docs/reference/rpc/google.cloud.speech.v1p1beta1#google.cloud.speech.v1p1beta1.SpeechContext + SpeechContext speechContext = + SpeechContext.newBuilder().addPhrases("Brooklyn Bridge").setBoost(20.0F).build(); + // Configure recognition config to match your audio file. + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(RecognitionConfig.AudioEncoding.MP3) + .setSampleRateHertz(44100) + .setLanguageCode("en-US") + .addSpeechContexts(speechContext) + .build(); + // Set the path to your audio file + RecognitionAudio audio = RecognitionAudio.newBuilder().setUri(uriPath).build(); + + // Make the request + RecognizeRequest request = + RecognizeRequest.newBuilder().setConfig(config).setAudio(audio).build(); + + // Display the results + RecognizeResponse response = speechClient.recognize(request); + for (SpeechRecognitionResult result : response.getResultsList()) { + // First alternative is the most probable result + SpeechRecognitionAlternative alternative = result.getAlternativesList().get(0); + System.out.printf("Transcript: %s\n", alternative.getTranscript()); + } + } + } +} +// [END speech_adaptation_beta] diff --git a/speech/src/main/java/com/example/speech/SpeechProfanityFilter.java b/speech/src/main/java/com/example/speech/SpeechProfanityFilter.java new file mode 100644 index 00000000000..b8ee99215d5 --- /dev/null +++ b/speech/src/main/java/com/example/speech/SpeechProfanityFilter.java @@ -0,0 +1,70 @@ +/* + * 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.speech; + +// [START speech_transcribe_with_profanity_filter_gcs] +import com.google.cloud.speech.v1.RecognitionAudio; +import com.google.cloud.speech.v1.RecognitionConfig; +import com.google.cloud.speech.v1.RecognitionConfig.AudioEncoding; +import com.google.cloud.speech.v1.RecognizeResponse; +import com.google.cloud.speech.v1.SpeechClient; +import com.google.cloud.speech.v1.SpeechRecognitionAlternative; +import com.google.cloud.speech.v1.SpeechRecognitionResult; +import java.util.List; + +public class SpeechProfanityFilter { + + public void speechProfanityFilter() throws Exception { + String uriPath = "gs://cloud-samples-tests/speech/brooklyn.flac"; + speechProfanityFilter(uriPath); + } + + /** + * Transcribe a remote audio file with multi-channel recognition + * + * @param gcsUri the path to the audio file + */ + public static void speechProfanityFilter(String gcsUri) throws Exception { + // Instantiates a client with GOOGLE_APPLICATION_CREDENTIALS + try (SpeechClient speech = SpeechClient.create()) { + + // Configure remote file request + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(AudioEncoding.FLAC) + .setLanguageCode("en-US") + .setSampleRateHertz(16000) + .setProfanityFilter(true) + .build(); + + // Set the remote path for the audio file + RecognitionAudio audio = RecognitionAudio.newBuilder().setUri(gcsUri).build(); + + // Use blocking call to get audio transcript + RecognizeResponse response = speech.recognize(config, audio); + List results = response.getResultsList(); + + for (SpeechRecognitionResult result : results) { + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternativesList().get(0); + System.out.printf("Transcription: %s\n", alternative.getTranscript()); + } + } + } +} +// [END speech_transcribe_with_profanity_filter_gcs] diff --git a/speech/src/main/java/com/example/speech/TranscribeDiarization.java b/speech/src/main/java/com/example/speech/TranscribeDiarization.java new file mode 100644 index 00000000000..6778f4c5907 --- /dev/null +++ b/speech/src/main/java/com/example/speech/TranscribeDiarization.java @@ -0,0 +1,98 @@ +/* + * 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.speech; + +// [START speech_transcribe_diarization] + +import com.google.cloud.speech.v1.RecognitionAudio; +import com.google.cloud.speech.v1.RecognitionConfig; +import com.google.cloud.speech.v1.RecognizeResponse; +import com.google.cloud.speech.v1.SpeakerDiarizationConfig; +import com.google.cloud.speech.v1.SpeechClient; +import com.google.cloud.speech.v1.SpeechRecognitionAlternative; +import com.google.cloud.speech.v1.WordInfo; +import com.google.protobuf.ByteString; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +class TranscribeDiarization { + + static void transcribeDiarization() throws IOException { + // TODO(developer): Replace these variables before running the sample. + String fileName = "resources/commercial_mono.wav"; + transcribeDiarization(fileName); + } + + // Transcribe the given audio file using speaker diarization. + static void transcribeDiarization(String fileName) throws IOException { + Path path = Paths.get(fileName); + byte[] content = Files.readAllBytes(path); + + // 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 (SpeechClient client = SpeechClient.create()) { + // Get the contents of the local audio file + RecognitionAudio recognitionAudio = + RecognitionAudio.newBuilder().setContent(ByteString.copyFrom(content)).build(); + SpeakerDiarizationConfig speakerDiarizationConfig = + SpeakerDiarizationConfig.newBuilder() + .setEnableSpeakerDiarization(true) + .setMinSpeakerCount(2) + .setMaxSpeakerCount(2) + .build(); + // Configure request to enable Speaker diarization + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(RecognitionConfig.AudioEncoding.LINEAR16) + .setLanguageCode("en-US") + .setSampleRateHertz(8000) + .setDiarizationConfig(speakerDiarizationConfig) + .build(); + + // Perform the transcription request + RecognizeResponse recognizeResponse = client.recognize(config, recognitionAudio); + + // Speaker Tags are only included in the last result object, which has only one alternative. + SpeechRecognitionAlternative alternative = + recognizeResponse.getResults(recognizeResponse.getResultsCount() - 1).getAlternatives(0); + // The alternative is made up of WordInfo objects that contain the speaker_tag. + WordInfo wordInfo = alternative.getWords(0); + int currentSpeakerTag = wordInfo.getSpeakerTag(); + // For each word, get all the words associated with one speaker, once the speaker changes, + // add a new line with the new speaker and their spoken words. + StringBuilder speakerWords = + new StringBuilder( + String.format("Speaker %d: %s", wordInfo.getSpeakerTag(), wordInfo.getWord())); + for (int i = 1; i < alternative.getWordsCount(); i++) { + wordInfo = alternative.getWords(i); + if (currentSpeakerTag == wordInfo.getSpeakerTag()) { + speakerWords.append(" "); + speakerWords.append(wordInfo.getWord()); + } else { + speakerWords.append( + String.format("\nSpeaker %d: %s", wordInfo.getSpeakerTag(), wordInfo.getWord())); + currentSpeakerTag = wordInfo.getSpeakerTag(); + } + } + System.out.println(speakerWords.toString()); + } + } +} +// [END speech_transcribe_diarization] diff --git a/speech/src/main/java/com/example/speech/TranscribeDiarizationGcs.java b/speech/src/main/java/com/example/speech/TranscribeDiarizationGcs.java new file mode 100644 index 00000000000..de7245b9a21 --- /dev/null +++ b/speech/src/main/java/com/example/speech/TranscribeDiarizationGcs.java @@ -0,0 +1,98 @@ +/* + * 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.speech; + +// [START speech_transcribe_diarization_gcs] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.speech.v1.LongRunningRecognizeMetadata; +import com.google.cloud.speech.v1.LongRunningRecognizeResponse; +import com.google.cloud.speech.v1.RecognitionAudio; +import com.google.cloud.speech.v1.RecognitionConfig; +import com.google.cloud.speech.v1.SpeakerDiarizationConfig; +import com.google.cloud.speech.v1.SpeechClient; +import com.google.cloud.speech.v1.SpeechRecognitionAlternative; +import com.google.cloud.speech.v1.WordInfo; +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +public class TranscribeDiarizationGcs { + + static void transcribeDiarizationGcs() + throws IOException, ExecutionException, InterruptedException { + // TODO(developer): Replace these variables before running the sample. + String gcsUri = "gs://cloud-samples-data/speech/commercial_mono.wav"; + transcribeDiarizationGcs(gcsUri); + } + + // Transcribe the give gcs file using speaker diarization + public static void transcribeDiarizationGcs(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 (SpeechClient speechClient = SpeechClient.create()) { + SpeakerDiarizationConfig speakerDiarizationConfig = + SpeakerDiarizationConfig.newBuilder() + .setEnableSpeakerDiarization(true) + .setMinSpeakerCount(2) + .setMaxSpeakerCount(2) + .build(); + // Configure request to enable Speaker diarization + RecognitionConfig config = + RecognitionConfig.newBuilder() + .setEncoding(RecognitionConfig.AudioEncoding.LINEAR16) + .setLanguageCode("en-US") + .setSampleRateHertz(8000) + .setDiarizationConfig(speakerDiarizationConfig) + .build(); + // Set the remote path for the audio file + RecognitionAudio audio = RecognitionAudio.newBuilder().setUri(gcsUri).build(); + + // Use non-blocking call for getting file transcription + OperationFuture future = + speechClient.longRunningRecognizeAsync(config, audio); + System.out.println("Waiting for response..."); + + // Speaker Tags are only included in the last result object, which has only one alternative. + LongRunningRecognizeResponse response = future.get(); + SpeechRecognitionAlternative alternative = + response.getResults(response.getResultsCount() - 1).getAlternatives(0); + // The alternative is made up of WordInfo objects that contain the speaker_tag. + WordInfo wordInfo = alternative.getWords(0); + int currentSpeakerTag = wordInfo.getSpeakerTag(); + // For each word, get all the words associated with one speaker, once the speaker changes, + // add a new line with the new speaker and their spoken words. + StringBuilder speakerWords = + new StringBuilder( + String.format("Speaker %d: %s", wordInfo.getSpeakerTag(), wordInfo.getWord())); + for (int i = 1; i < alternative.getWordsCount(); i++) { + wordInfo = alternative.getWords(i); + if (currentSpeakerTag == wordInfo.getSpeakerTag()) { + speakerWords.append(" "); + speakerWords.append(wordInfo.getWord()); + } else { + speakerWords.append( + String.format("\nSpeaker %d: %s", wordInfo.getSpeakerTag(), wordInfo.getWord())); + currentSpeakerTag = wordInfo.getSpeakerTag(); + } + } + System.out.println(speakerWords.toString()); + } + } +} +// [END speech_transcribe_diarization_gcs] diff --git a/speech/src/test/java/com/example/speech/QuickstartSampleIT.java b/speech/src/test/java/com/example/speech/QuickstartSampleIT.java new file mode 100644 index 00000000000..ed739930161 --- /dev/null +++ b/speech/src/test/java/com/example/speech/QuickstartSampleIT.java @@ -0,0 +1,57 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.speech; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for quickstart sample. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class QuickstartSampleIT { + 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 testQuickstart() throws Exception { + // Act + QuickstartSample.main(); + + // Assert + String got = bout.toString(); + assertThat(got).contains("how old is the Brooklyn Bridge"); + } +} diff --git a/speech/src/test/java/com/example/speech/RecognizeBetaIT.java b/speech/src/test/java/com/example/speech/RecognizeBetaIT.java new file mode 100644 index 00000000000..17fe91f1f12 --- /dev/null +++ b/speech/src/test/java/com/example/speech/RecognizeBetaIT.java @@ -0,0 +1,129 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.speech; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for speech recognize sample. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class RecognizeBetaIT { + private static final String BUCKET = "cloud-samples-data"; + + private ByteArrayOutputStream bout; + private PrintStream out; + + // The path to the audio file to transcribe + private String audioFileName = "./resources/audio.raw"; + private String multiChannelAudioFileName = "./resources/commercial_stereo.wav"; + private String gcsMultiChannelAudioPath = "gs://" + BUCKET + "/speech/commercial_stereo.wav"; + private String gcsAudioPath = "gs://" + BUCKET + "/speech/brooklyn_bridge.flac"; + private String gcsDiarizationAudioPath = "gs://" + BUCKET + "/speech/commercial_mono.wav"; + + // The path to the video file to transcribe + private String videoFileName = "./resources/Google_Gnome.wav"; + private String gcsVideoPath = "gs://" + BUCKET + "/speech/Google_Gnome.wav"; + + private String recognitionAudioFile = "./resources/commercial_mono.wav"; + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() { + System.setOut(null); + } + + @Test + public void testMetadata() throws Exception { + RecognizeBeta.transcribeFileWithMetadata(recognitionAudioFile); + String got = bout.toString(); + assertThat(got).contains("Chrome"); + } + + @Test + public void testTranscribeDiarization() throws Exception { + RecognizeBeta.transcribeDiarization(recognitionAudioFile); + String got = bout.toString(); + // Diarization (a beta product) can be flaky, therefore this test is only looking for output + assertThat(got).contains("Speaker"); + } + + @Test + public void testTranscribeDiarizationGcs() throws Exception { + RecognizeBeta.transcribeDiarizationGcs(gcsDiarizationAudioPath); + String got = bout.toString(); + // Diarization (a beta product) can be flaky, therefore this test is only looking for output + assertThat(got).contains("Speaker"); + } + + @Test + public void testTranscribeMultiChannel() throws Exception { + RecognizeBeta.transcribeMultiChannel(multiChannelAudioFileName); + String got = bout.toString(); + assertThat(got).contains("Channel Tag : 1"); + } + + @Test + public void testTranscribeMultiChannelGcs() throws Exception { + RecognizeBeta.transcribeMultiChannelGcs(gcsMultiChannelAudioPath); + String got = bout.toString(); + assertThat(got).contains("Channel Tag : 1"); + } + + @Test + public void testTranscribeMultiLanguage() throws Exception { + RecognizeBeta.transcribeMultiLanguage(videoFileName); + String got = bout.toString(); + assertThat(got).contains("Transcript : OK Google"); + } + + @Test + public void testTranscribeMultiLanguageGcs() throws Exception { + RecognizeBeta.transcribeMultiLanguageGcs(gcsVideoPath); + String got = bout.toString(); + assertThat(got).contains("Transcript : OK Google"); + } + + @Test + public void testTranscribeWordLevelConfidence() throws Exception { + RecognizeBeta.transcribeWordLevelConfidence(audioFileName); + String got = bout.toString(); + assertThat(got).contains("Transcript : how old is the Brooklyn Bridge"); + assertThat(got).contains("First Word and Confidence : how"); + } + + @Test + public void testTranscribeWordLevelConfidenceGcs() throws Exception { + RecognizeBeta.transcribeWordLevelConfidenceGcs(gcsAudioPath); + String got = bout.toString(); + assertThat(got).contains("Transcript : how old is the Brooklyn Bridge"); + assertThat(got).contains("First Word and Confidence : how"); + } +} diff --git a/speech/src/test/java/com/example/speech/RecognizeIT.java b/speech/src/test/java/com/example/speech/RecognizeIT.java new file mode 100644 index 00000000000..2de1b0a1b45 --- /dev/null +++ b/speech/src/test/java/com/example/speech/RecognizeIT.java @@ -0,0 +1,170 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.speech; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for speech recognize sample. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class RecognizeIT { + private static final String BUCKET = "cloud-samples-tests"; + + private ByteArrayOutputStream bout; + private PrintStream out; + + // The path to the audio file to transcribe + private String audioFileName = "./resources/audio.raw"; + private String multiChannelAudioFileName = "./resources/commercial_stereo.wav"; + private String gcsAudioPath = "gs://" + BUCKET + "/speech/brooklyn.flac"; + private String gcsMultiChannelAudioPath = "gs://" + BUCKET + "/speech/commercial_stereo.wav"; + + private String recognitionAudioFile = "./resources/commercial_mono.wav"; + + // The path to the video file to transcribe + private String videoFileName = "./resources/Google_Gnome.wav"; + private String gcsVideoPath = "gs://" + BUCKET + "/speech/Google_Gnome.wav"; + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() { + System.setOut(null); + } + + @Test + public void testRecognizeFile() throws Exception { + Recognize.syncRecognizeFile(audioFileName); + String got = bout.toString(); + assertThat(got).contains("how old is the Brooklyn Bridge"); + } + + @Test + public void testRecognizeWordoffset() throws Exception { + Recognize.syncRecognizeWords(audioFileName); + String got = bout.toString(); + assertThat(got).contains("how old is the Brooklyn Bridge"); + assertThat(got).contains("\t0.0 sec -"); + } + + @Test + public void testRecognizeGcs() throws Exception { + Recognize.syncRecognizeGcs(gcsAudioPath); + String got = bout.toString(); + assertThat(got).contains("how old is the Brooklyn Bridge"); + } + + @Test + public void testAsyncRecognizeFile() throws Exception { + Recognize.asyncRecognizeFile(audioFileName); + String got = bout.toString(); + assertThat(got).contains("how old is the Brooklyn Bridge"); + } + + @Test + public void testAsyncRecognizeGcs() throws Exception { + Recognize.asyncRecognizeGcs(gcsAudioPath); + String got = bout.toString(); + assertThat(got).contains("how old is the Brooklyn Bridge"); + } + + @Test + public void testAsyncWordoffset() throws Exception { + Recognize.asyncRecognizeWords(gcsAudioPath); + String got = bout.toString(); + assertThat(got).contains("how old is the Brooklyn Bridge"); + assertThat(got).contains("\t0.0 sec -"); + } + + @Test + public void testStreamRecognize() throws Exception { + Recognize.streamingRecognizeFile(audioFileName); + String got = bout.toString(); + assertThat(got).contains("how old is the Brooklyn Bridge"); + } + + @Test + public void testAutoPunctuation() throws Exception { + Recognize.transcribeFileWithAutomaticPunctuation(audioFileName); + String got = bout.toString(); + assertThat(got).contains("Transcript"); + } + + @Test + public void testGcsAutoPunctuation() throws Exception { + Recognize.transcribeGcsWithAutomaticPunctuation(gcsAudioPath); + String got = bout.toString(); + assertThat(got).contains("Transcript"); + } + + @Test + public void testStreamAutoPunctuation() throws Exception { + Recognize.streamingTranscribeWithAutomaticPunctuation(audioFileName); + String got = bout.toString(); + assertThat(got).contains("Transcript"); + } + + @Test + public void testEnhancedModel() throws Exception { + Recognize.transcribeFileWithEnhancedModel(recognitionAudioFile); + String got = bout.toString(); + assertThat(got).contains("Chrome"); + } + + @Test + public void testModelSelection() throws Exception { + Recognize.transcribeModelSelection(videoFileName); + String got = bout.toString(); + assertThat(got).contains("OK Google"); + assertThat(got).contains("the weather outside is sunny"); + } + + @Test + public void testGcsModelSelection() throws Exception { + Recognize.transcribeModelSelectionGcs(gcsVideoPath); + String got = bout.toString(); + assertThat(got).contains("OK Google"); + assertThat(got).contains("the weather outside is sunny"); + } + + @Test + public void testTranscribeMultiChannel() throws Exception { + Recognize.transcribeMultiChannel(multiChannelAudioFileName); + String got = bout.toString(); + assertThat(got).contains("Channel Tag : 1"); + } + + @Test + public void testTranscribeMultiChannelGcs() throws Exception { + Recognize.transcribeMultiChannelGcs(gcsMultiChannelAudioPath); + String got = bout.toString(); + assertThat(got).contains("Channel Tag : 1"); + } +} diff --git a/speech/src/test/java/com/example/speech/SpeechAdaptationTest.java b/speech/src/test/java/com/example/speech/SpeechAdaptationTest.java new file mode 100644 index 00000000000..a31b3637d5d --- /dev/null +++ b/speech/src/test/java/com/example/speech/SpeechAdaptationTest.java @@ -0,0 +1,55 @@ +/* + * 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.speech; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class SpeechAdaptationTest { + private static final String AUDIO_FILE = "gs://cloud-samples-data/speech/brooklyn_bridge.mp3"; + 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 testTranscribeContextClasses() throws IOException { + SpeechAdaptation.speechAdaptation(AUDIO_FILE); + String got = bout.toString(); + assertThat(got).contains("Transcript:"); + } +} diff --git a/speech/src/test/java/com/example/speech/SpeechProfanityFilterTest.java b/speech/src/test/java/com/example/speech/SpeechProfanityFilterTest.java new file mode 100644 index 00000000000..ddf1ccaeecb --- /dev/null +++ b/speech/src/test/java/com/example/speech/SpeechProfanityFilterTest.java @@ -0,0 +1,56 @@ +/* + * 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.speech; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class SpeechProfanityFilterTest { + private static final String AUDIO_FILE = "gs://cloud-samples-tests/speech/brooklyn.flac"; + private ByteArrayOutputStream bout; + private PrintStream stdout; + private PrintStream out; + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + stdout = System.out; + System.setOut(out); + } + + @After + public void tearDown() { + System.setOut(stdout); + } + + @Test + public void testSpeechProfanityFilter() throws Exception { + SpeechProfanityFilter.speechProfanityFilter(AUDIO_FILE); + String got = bout.toString(); + assertThat(got).contains("how old is the Brooklyn Bridge"); + } +} diff --git a/speech/src/test/java/com/example/speech/TranscribeDiarizationIT.java b/speech/src/test/java/com/example/speech/TranscribeDiarizationIT.java new file mode 100644 index 00000000000..ce69cdd2286 --- /dev/null +++ b/speech/src/test/java/com/example/speech/TranscribeDiarizationIT.java @@ -0,0 +1,80 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.speech; + +import static com.google.common.truth.Truth.assertThat; +import static junit.framework.TestCase.assertNotNull; + +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.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +// Tests for speech Transcribe Diarization samples. +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class TranscribeDiarizationIT { + private ByteArrayOutputStream bout; + private PrintStream out; + + // The path to the audio file to transcribe + private String recognitionAudioFile = "./resources/commercial_mono.wav"; + + private static void requireEnvVar(String varName) { + assertNotNull( + System.getenv(varName), + "Environment variable '%s' is required to perform these tests.".format(varName)); + } + + @BeforeClass + public static void checkRequirements() { + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + } + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() { + System.setOut(null); + } + + @Test + public void testDiarization() throws IOException { + TranscribeDiarization.transcribeDiarization(recognitionAudioFile); + String got = bout.toString(); + assertThat(got).contains("Speaker"); + } + + @Test + public void testDiarizationGcs() throws IOException, ExecutionException, InterruptedException { + TranscribeDiarizationGcs.transcribeDiarizationGcs( + "gs://cloud-samples-data/speech/commercial_mono.wav"); + String got = bout.toString(); + assertThat(got).contains("Speaker"); + } +} diff --git a/storage/xml-api/serviceaccount-appengine-sample/pom.xml b/storage/xml-api/serviceaccount-appengine-sample/pom.xml index f4000533aae..f95e80729e5 100644 --- a/storage/xml-api/serviceaccount-appengine-sample/pom.xml +++ b/storage/xml-api/serviceaccount-appengine-sample/pom.xml @@ -35,7 +35,7 @@ 1.8 1.8 - 1.35.2 + 2.0.1 ${project.build.directory}/${project.build.finalName} UTF-8 diff --git a/texttospeech/snippets/pom.xml b/texttospeech/snippets/pom.xml new file mode 100644 index 00000000000..19a0cf5853b --- /dev/null +++ b/texttospeech/snippets/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + com.example.texttospeech + texttospeech-snippets + jar + Google Cloud Text-to-Speech Snippets + https://github.com/GoogleCloudPlatform/java-docs-samples/tree/main/texttospeech + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + 1.8 + 1.8 + UTF-8 + + + + + + + + com.google.cloud + libraries-bom + 26.1.4 + pom + import + + + + + + + com.google.cloud + google-cloud-texttospeech + + + + + net.sourceforge.argparse4j + argparse4j + 0.9.0 + + + junit + junit + 4.13.2 + test + + + com.google.truth + truth + 1.1.3 + test + + + + + + + diff --git a/texttospeech/snippets/resources/example.ssml b/texttospeech/snippets/resources/example.ssml new file mode 100644 index 00000000000..c27fd399967 --- /dev/null +++ b/texttospeech/snippets/resources/example.ssml @@ -0,0 +1,4 @@ +123 Street Ln, Small Town, IL 12345 USA +1 Jenny St & Number St, Tutone City, CA 86753 +1 Piazza del Fibonacci, 12358 Pisa, Italy + \ No newline at end of file diff --git a/texttospeech/snippets/resources/example.txt b/texttospeech/snippets/resources/example.txt new file mode 100644 index 00000000000..9cd7d74db36 --- /dev/null +++ b/texttospeech/snippets/resources/example.txt @@ -0,0 +1,3 @@ +123 Street Ln, Small Town, IL 12345 USA +1 Jenny St & Number St, Tutone City, CA 86753 +1 Piazza del Fibonacci, 12358 Pisa, Italy diff --git a/texttospeech/snippets/resources/expected_example.mp3 b/texttospeech/snippets/resources/expected_example.mp3 new file mode 100644 index 00000000000..407b85f7f5d Binary files /dev/null and b/texttospeech/snippets/resources/expected_example.mp3 differ diff --git a/texttospeech/snippets/resources/hello.ssml b/texttospeech/snippets/resources/hello.ssml new file mode 100644 index 00000000000..df7bf9eee37 --- /dev/null +++ b/texttospeech/snippets/resources/hello.ssml @@ -0,0 +1 @@ +Hello there. diff --git a/texttospeech/snippets/resources/hello.txt b/texttospeech/snippets/resources/hello.txt new file mode 100644 index 00000000000..495cc9fa8f9 --- /dev/null +++ b/texttospeech/snippets/resources/hello.txt @@ -0,0 +1 @@ +Hello there! diff --git a/texttospeech/snippets/src/main/java/com/example/texttospeech/ListAllSupportedVoices.java b/texttospeech/snippets/src/main/java/com/example/texttospeech/ListAllSupportedVoices.java new file mode 100644 index 00000000000..fff4c7bbe59 --- /dev/null +++ b/texttospeech/snippets/src/main/java/com/example/texttospeech/ListAllSupportedVoices.java @@ -0,0 +1,69 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.texttospeech; + +// Imports the Google Cloud client library +import com.google.cloud.texttospeech.v1.ListVoicesRequest; +import com.google.cloud.texttospeech.v1.ListVoicesResponse; +import com.google.cloud.texttospeech.v1.TextToSpeechClient; +import com.google.cloud.texttospeech.v1.Voice; +import com.google.protobuf.ByteString; +import java.util.List; + +/** + * Google Cloud TextToSpeech API sample application. Example usage: mvn package exec:java + * -Dexec.mainClass='com.example.texttospeech.ListAllSupportedVoices' + */ +public class ListAllSupportedVoices { + + // [START tts_list_voices] + /** + * Demonstrates using the Text to Speech client to list the client's supported voices. + * + * @throws Exception on TextToSpeechClient Errors. + */ + public static List listAllSupportedVoices() throws Exception { + // Instantiates a client + try (TextToSpeechClient textToSpeechClient = TextToSpeechClient.create()) { + // Builds the text to speech list voices request + ListVoicesRequest request = ListVoicesRequest.getDefaultInstance(); + + // Performs the list voices request + ListVoicesResponse response = textToSpeechClient.listVoices(request); + List voices = response.getVoicesList(); + + for (Voice voice : voices) { + // Display the voice's name. Example: tpc-vocoded + System.out.format("Name: %s\n", voice.getName()); + + // Display the supported language codes for this voice. Example: "en-us" + List languageCodes = voice.getLanguageCodesList().asByteStringList(); + for (ByteString languageCode : languageCodes) { + System.out.format("Supported Language: %s\n", languageCode.toStringUtf8()); + } + + // Display the SSML Voice Gender + System.out.format("SSML Voice Gender: %s\n", voice.getSsmlGender()); + + // Display the natural sample rate hertz for this voice. Example: 24000 + System.out.format("Natural Sample Rate Hertz: %s\n\n", voice.getNaturalSampleRateHertz()); + } + return voices; + } + } + // [END tts_list_voices] +} diff --git a/texttospeech/snippets/src/main/java/com/example/texttospeech/ListAllSupportedVoicesBeta.java b/texttospeech/snippets/src/main/java/com/example/texttospeech/ListAllSupportedVoicesBeta.java new file mode 100644 index 00000000000..7d648639142 --- /dev/null +++ b/texttospeech/snippets/src/main/java/com/example/texttospeech/ListAllSupportedVoicesBeta.java @@ -0,0 +1,72 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.texttospeech; + +// Imports the Google Cloud client library +import com.google.cloud.texttospeech.v1beta1.ListVoicesRequest; +import com.google.cloud.texttospeech.v1beta1.ListVoicesResponse; +import com.google.cloud.texttospeech.v1beta1.TextToSpeechClient; +import com.google.cloud.texttospeech.v1beta1.Voice; +import com.google.protobuf.ByteString; +import java.util.List; + +/** + * Google Cloud TextToSpeech API sample application. Example usage: mvn package exec:java + * -Dexec.mainClass='com.example.texttospeech.ListAllSupportedVoicesBeta' + */ +public class ListAllSupportedVoicesBeta { + + // [START tts_list_voices] + /** + * Demonstrates using the Text to Speech client to list the client's supported voices. + * + * @throws Exception on TextToSpeechClient Errors. + */ + public static void listAllSupportedVoices() throws Exception { + // Instantiates a client + try (TextToSpeechClient textToSpeechClient = TextToSpeechClient.create()) { + // Builds the text to speech list voices request + ListVoicesRequest request = ListVoicesRequest.getDefaultInstance(); + + // Performs the list voices request + ListVoicesResponse response = textToSpeechClient.listVoices(request); + List voices = response.getVoicesList(); + + for (Voice voice : voices) { + // Display the voice's name. Example: tpc-vocoded + System.out.format("Name: %s\n", voice.getName()); + + // Display the supported language codes for this voice. Example: "en-us" + List languageCodes = voice.getLanguageCodesList().asByteStringList(); + for (ByteString languageCode : languageCodes) { + System.out.format("Supported Language: %s\n", languageCode.toStringUtf8()); + } + + // Display the SSML Voice Gender + System.out.format("SSML Voice Gender: %s\n", voice.getSsmlGender()); + + // Display the natural sample rate hertz for this voice. Example: 24000 + System.out.format("Natural Sample Rate Hertz: %s\n\n", voice.getNaturalSampleRateHertz()); + } + } + } + // [END tts_list_voices] + + public static void main(String[] args) throws Exception { + listAllSupportedVoices(); + } +} diff --git a/texttospeech/snippets/src/main/java/com/example/texttospeech/QuickstartSample.java b/texttospeech/snippets/src/main/java/com/example/texttospeech/QuickstartSample.java new file mode 100644 index 00000000000..73841cb1e02 --- /dev/null +++ b/texttospeech/snippets/src/main/java/com/example/texttospeech/QuickstartSample.java @@ -0,0 +1,73 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.texttospeech; + +// [START tts_quickstart] +// Imports the Google Cloud client library +import com.google.cloud.texttospeech.v1.AudioConfig; +import com.google.cloud.texttospeech.v1.AudioEncoding; +import com.google.cloud.texttospeech.v1.SsmlVoiceGender; +import com.google.cloud.texttospeech.v1.SynthesisInput; +import com.google.cloud.texttospeech.v1.SynthesizeSpeechResponse; +import com.google.cloud.texttospeech.v1.TextToSpeechClient; +import com.google.cloud.texttospeech.v1.VoiceSelectionParams; +import com.google.protobuf.ByteString; +import java.io.FileOutputStream; +import java.io.OutputStream; + +/** + * Google Cloud TextToSpeech API sample application. Example usage: mvn package exec:java + * -Dexec.mainClass='com.example.texttospeech.QuickstartSample' + */ +public class QuickstartSample { + + /** Demonstrates using the Text-to-Speech API. */ + public static void main(String... args) throws Exception { + // Instantiates a client + try (TextToSpeechClient textToSpeechClient = TextToSpeechClient.create()) { + // Set the text input to be synthesized + SynthesisInput input = SynthesisInput.newBuilder().setText("Hello, World!").build(); + + // Build the voice request, select the language code ("en-US") and the ssml voice gender + // ("neutral") + VoiceSelectionParams voice = + VoiceSelectionParams.newBuilder() + .setLanguageCode("en-US") + .setSsmlGender(SsmlVoiceGender.NEUTRAL) + .build(); + + // Select the type of audio file you want returned + AudioConfig audioConfig = + AudioConfig.newBuilder().setAudioEncoding(AudioEncoding.MP3).build(); + + // Perform the text-to-speech request on the text input with the selected voice parameters and + // audio file type + SynthesizeSpeechResponse response = + textToSpeechClient.synthesizeSpeech(input, voice, audioConfig); + + // Get the audio contents from the response + ByteString audioContents = response.getAudioContent(); + + // Write the response to the output file. + try (OutputStream out = new FileOutputStream("output.mp3")) { + out.write(audioContents.toByteArray()); + System.out.println("Audio content written to file \"output.mp3\""); + } + } + } +} +// [END tts_quickstart] diff --git a/texttospeech/snippets/src/main/java/com/example/texttospeech/QuickstartSampleBeta.java b/texttospeech/snippets/src/main/java/com/example/texttospeech/QuickstartSampleBeta.java new file mode 100644 index 00000000000..0f520c86e85 --- /dev/null +++ b/texttospeech/snippets/src/main/java/com/example/texttospeech/QuickstartSampleBeta.java @@ -0,0 +1,73 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.texttospeech; + +// [START tts_quickstart] +// Imports the Google Cloud client library +import com.google.cloud.texttospeech.v1beta1.AudioConfig; +import com.google.cloud.texttospeech.v1beta1.AudioEncoding; +import com.google.cloud.texttospeech.v1beta1.SsmlVoiceGender; +import com.google.cloud.texttospeech.v1beta1.SynthesisInput; +import com.google.cloud.texttospeech.v1beta1.SynthesizeSpeechResponse; +import com.google.cloud.texttospeech.v1beta1.TextToSpeechClient; +import com.google.cloud.texttospeech.v1beta1.VoiceSelectionParams; +import com.google.protobuf.ByteString; +import java.io.FileOutputStream; +import java.io.OutputStream; + +/** + * Google Cloud TextToSpeech API sample application. Example usage: mvn package exec:java + * -Dexec.mainClass='com.example.texttospeech.QuickstartSampleBeta' + */ +public class QuickstartSampleBeta { + + /** Demonstrates using the Text-to-Speech API. */ + public static void main(String... args) throws Exception { + // Instantiates a client + try (TextToSpeechClient textToSpeechClient = TextToSpeechClient.create()) { + // Set the text input to be synthesized + SynthesisInput input = SynthesisInput.newBuilder().setText("Hello, World!").build(); + + // Build the voice request, select the language code ("en-US") and the ssml voice gender + // ("neutral") + VoiceSelectionParams voice = + VoiceSelectionParams.newBuilder() + .setLanguageCode("en-US") + .setSsmlGender(SsmlVoiceGender.NEUTRAL) + .build(); + + // Select the type of audio file you want returned + AudioConfig audioConfig = + AudioConfig.newBuilder().setAudioEncoding(AudioEncoding.MP3).build(); + + // Perform the text-to-speech request on the text input with the selected voice parameters and + // audio file type + SynthesizeSpeechResponse response = + textToSpeechClient.synthesizeSpeech(input, voice, audioConfig); + + // Get the audio contents from the response + ByteString audioContents = response.getAudioContent(); + + // Write the response to the output file. + try (OutputStream out = new FileOutputStream("output.mp3")) { + out.write(audioContents.toByteArray()); + System.out.println("Audio content written to file \"output.mp3\""); + } + } + } +} +// [END tts_quickstart] diff --git a/texttospeech/snippets/src/main/java/com/example/texttospeech/SsmlAddresses.java b/texttospeech/snippets/src/main/java/com/example/texttospeech/SsmlAddresses.java new file mode 100644 index 00000000000..d5192429148 --- /dev/null +++ b/texttospeech/snippets/src/main/java/com/example/texttospeech/SsmlAddresses.java @@ -0,0 +1,134 @@ +/* + * Copyright 2019 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.texttospeech; + +// [START tts_ssml_address_imports] +// Imports the Google Cloud client library +import com.google.cloud.texttospeech.v1.AudioConfig; +import com.google.cloud.texttospeech.v1.AudioEncoding; +import com.google.cloud.texttospeech.v1.SsmlVoiceGender; +import com.google.cloud.texttospeech.v1.SynthesisInput; +import com.google.cloud.texttospeech.v1.SynthesizeSpeechResponse; +import com.google.cloud.texttospeech.v1.TextToSpeechClient; +import com.google.cloud.texttospeech.v1.VoiceSelectionParams; +import com.google.common.html.HtmlEscapers; +import com.google.protobuf.ByteString; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Paths; + +// [END tts_ssml_address_imports] + +/** + * Google Cloud TextToSpeech API sample application. Example usage: mvn package exec:java + * -Dexec.mainClass='com.example.texttospeech.SsmlAddresses + */ +public class SsmlAddresses { + + // [START tts_ssml_address_audio] + /** + * Generates synthetic audio from a String of SSML text. + * + *

Given a string of SSML text and an output file name, this function calls the Text-to-Speech + * API. The API returns a synthetic audio version of the text, formatted according to the SSML + * commands. This function saves the synthetic audio to the designated output file. + * + * @param ssmlText String of tagged SSML text + * @param outFile String name of file under which to save audio output + * @throws Exception on errors while closing the client + */ + public static void ssmlToAudio(String ssmlText, String outFile) throws Exception { + // Instantiates a client + try (TextToSpeechClient textToSpeechClient = TextToSpeechClient.create()) { + // Set the ssml text input to synthesize + SynthesisInput input = SynthesisInput.newBuilder().setSsml(ssmlText).build(); + + // Build the voice request, select the language code ("en-US") and + // the ssml voice gender ("male") + VoiceSelectionParams voice = + VoiceSelectionParams.newBuilder() + .setLanguageCode("en-US") + .setSsmlGender(SsmlVoiceGender.MALE) + .build(); + + // Select the audio file type + AudioConfig audioConfig = + AudioConfig.newBuilder().setAudioEncoding(AudioEncoding.MP3).build(); + + // Perform the text-to-speech request on the text input with the selected voice parameters and + // audio file type + SynthesizeSpeechResponse response = + textToSpeechClient.synthesizeSpeech(input, voice, audioConfig); + + // Get the audio contents from the response + ByteString audioContents = response.getAudioContent(); + + // Write the response to the output file + try (OutputStream out = new FileOutputStream(outFile)) { + out.write(audioContents.toByteArray()); + System.out.println("Audio content written to file " + outFile); + } + } + } + // [END tts_ssml_address_audio] + + // [START tts_ssml_address_ssml] + /** + * Generates SSML text from plaintext. + * + *

Given an input filename, this function converts the contents of the input text file into a + * String of tagged SSML text. This function formats the SSML String so that, when synthesized, + * the synthetic audio will pause for two seconds between each line of the text file. This + * function also handles special text characters which might interfere with SSML commands. + * + * @param inputFile String name of plaintext file + * @return a String of SSML text based on plaintext input. + * @throws IOException on files that don't exist + */ + public static String textToSsml(String inputFile) throws Exception { + + // Read lines of input file + String rawLines = new String(Files.readAllBytes(Paths.get(inputFile))); + + // Replace special characters with HTML Ampersand Character Codes + // These codes prevent the API from confusing text with SSML tags + // For example, '<' --> '<' and '&' --> '&' + String escapedLines = HtmlEscapers.htmlEscaper().escape(rawLines); + + // Convert plaintext to SSML + // Tag SSML so that there is a 2 second pause between each address + String expandedNewline = escapedLines.replaceAll("\\n", "\n"); + String ssml = "" + expandedNewline + ""; + + // Return the concatenated String of SSML + return ssml; + } + // [END tts_ssml_address_ssml] + + // [START tts_ssml_address_test] + public static void main(String... args) throws Exception { + // test example address file + String inputFile = "resources/example.txt"; + String outFile = "resources/example.mp3"; + + String ssml = textToSsml(inputFile); + ssmlToAudio(ssml, outFile); + } + // [END tts_ssml_address_test] +} diff --git a/texttospeech/snippets/src/main/java/com/example/texttospeech/SynthesizeFile.java b/texttospeech/snippets/src/main/java/com/example/texttospeech/SynthesizeFile.java new file mode 100644 index 00000000000..b67c04df69d --- /dev/null +++ b/texttospeech/snippets/src/main/java/com/example/texttospeech/SynthesizeFile.java @@ -0,0 +1,129 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.texttospeech; + +// Imports the Google Cloud client library +import com.google.cloud.texttospeech.v1.AudioConfig; +import com.google.cloud.texttospeech.v1.AudioEncoding; +import com.google.cloud.texttospeech.v1.SsmlVoiceGender; +import com.google.cloud.texttospeech.v1.SynthesisInput; +import com.google.cloud.texttospeech.v1.SynthesizeSpeechResponse; +import com.google.cloud.texttospeech.v1.TextToSpeechClient; +import com.google.cloud.texttospeech.v1.VoiceSelectionParams; +import com.google.protobuf.ByteString; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Paths; + +/** + * Google Cloud TextToSpeech API sample application. Example usage: mvn package exec:java + * -Dexec.mainClass='com.example.texttospeech.SynthesizeFile' -Dexec.args='--text + * resources/hello.txt' + */ +public class SynthesizeFile { + + // [START tts_synthesize_text_file] + /** + * Demonstrates using the Text to Speech client to synthesize a text file or ssml file. + * + * @param textFile the text file to be synthesized. (e.g., hello.txt) + * @throws Exception on TextToSpeechClient Errors. + */ + public static ByteString synthesizeTextFile(String textFile) throws Exception { + // Instantiates a client + try (TextToSpeechClient textToSpeechClient = TextToSpeechClient.create()) { + // Read the file's contents + String contents = new String(Files.readAllBytes(Paths.get(textFile))); + // Set the text input to be synthesized + SynthesisInput input = SynthesisInput.newBuilder().setText(contents).build(); + + // Build the voice request + VoiceSelectionParams voice = + VoiceSelectionParams.newBuilder() + .setLanguageCode("en-US") // languageCode = "en_us" + .setSsmlGender(SsmlVoiceGender.FEMALE) // ssmlVoiceGender = SsmlVoiceGender.FEMALE + .build(); + + // Select the type of audio file you want returned + AudioConfig audioConfig = + AudioConfig.newBuilder() + .setAudioEncoding(AudioEncoding.MP3) // MP3 audio. + .build(); + + // Perform the text-to-speech request + SynthesizeSpeechResponse response = + textToSpeechClient.synthesizeSpeech(input, voice, audioConfig); + + // Get the audio contents from the response + ByteString audioContents = response.getAudioContent(); + + // Write the response to the output file. + try (OutputStream out = new FileOutputStream("output.mp3")) { + out.write(audioContents.toByteArray()); + System.out.println("Audio content written to file \"output.mp3\""); + return audioContents; + } + } + } + // [END tts_synthesize_text_file] + + // [START tts_synthesize_ssml_file] + /** + * Demonstrates using the Text to Speech client to synthesize a text file or ssml file. + * + * @param ssmlFile the ssml document to be synthesized. (e.g., hello.ssml) + * @throws Exception on TextToSpeechClient Errors. + */ + public static ByteString synthesizeSsmlFile(String ssmlFile) throws Exception { + // Instantiates a client + try (TextToSpeechClient textToSpeechClient = TextToSpeechClient.create()) { + // Read the file's contents + String contents = new String(Files.readAllBytes(Paths.get(ssmlFile))); + // Set the ssml input to be synthesized + SynthesisInput input = SynthesisInput.newBuilder().setSsml(contents).build(); + + // Build the voice request + VoiceSelectionParams voice = + VoiceSelectionParams.newBuilder() + .setLanguageCode("en-US") // languageCode = "en_us" + .setSsmlGender(SsmlVoiceGender.FEMALE) // ssmlVoiceGender = SsmlVoiceGender.FEMALE + .build(); + + // Select the type of audio file you want returned + AudioConfig audioConfig = + AudioConfig.newBuilder() + .setAudioEncoding(AudioEncoding.MP3) // MP3 audio. + .build(); + + // Perform the text-to-speech request + SynthesizeSpeechResponse response = + textToSpeechClient.synthesizeSpeech(input, voice, audioConfig); + + // Get the audio contents from the response + ByteString audioContents = response.getAudioContent(); + + // Write the response to the output file. + try (OutputStream out = new FileOutputStream("output.mp3")) { + out.write(audioContents.toByteArray()); + System.out.println("Audio content written to file \"output.mp3\""); + return audioContents; + } + } + } + // [END tts_synthesize_ssml_file] +} diff --git a/texttospeech/snippets/src/main/java/com/example/texttospeech/SynthesizeFileBeta.java b/texttospeech/snippets/src/main/java/com/example/texttospeech/SynthesizeFileBeta.java new file mode 100644 index 00000000000..bf367cb3b6b --- /dev/null +++ b/texttospeech/snippets/src/main/java/com/example/texttospeech/SynthesizeFileBeta.java @@ -0,0 +1,155 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.texttospeech; + +// Imports the Google Cloud client library +import com.google.cloud.texttospeech.v1beta1.AudioConfig; +import com.google.cloud.texttospeech.v1beta1.AudioEncoding; +import com.google.cloud.texttospeech.v1beta1.SsmlVoiceGender; +import com.google.cloud.texttospeech.v1beta1.SynthesisInput; +import com.google.cloud.texttospeech.v1beta1.SynthesizeSpeechResponse; +import com.google.cloud.texttospeech.v1beta1.TextToSpeechClient; +import com.google.cloud.texttospeech.v1beta1.VoiceSelectionParams; +import com.google.protobuf.ByteString; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import net.sourceforge.argparse4j.ArgumentParsers; +import net.sourceforge.argparse4j.inf.ArgumentParser; +import net.sourceforge.argparse4j.inf.ArgumentParserException; +import net.sourceforge.argparse4j.inf.MutuallyExclusiveGroup; +import net.sourceforge.argparse4j.inf.Namespace; + +/** + * Google Cloud TextToSpeech API sample application. Example usage: mvn package exec:java + * -Dexec.mainClass='com.example.texttospeech.SynthesizeFile' -Dexec.args='--text + * resources/hello.txt' + */ +public class SynthesizeFileBeta { + + // [START tts_synthesize_text_file] + /** + * Demonstrates using the Text to Speech client to synthesize a text file or ssml file. + * + * @param textFile the text file to be synthesized. (e.g., hello.txt) + * @throws Exception on TextToSpeechClient Errors. + */ + public static void synthesizeTextFile(String textFile) throws Exception { + // Instantiates a client + try (TextToSpeechClient textToSpeechClient = TextToSpeechClient.create()) { + // Read the file's contents + String contents = new String(Files.readAllBytes(Paths.get(textFile))); + // Set the text input to be synthesized + SynthesisInput input = SynthesisInput.newBuilder().setText(contents).build(); + + // Build the voice request + VoiceSelectionParams voice = + VoiceSelectionParams.newBuilder() + .setLanguageCode("en-US") // languageCode = "en_us" + .setSsmlGender(SsmlVoiceGender.FEMALE) // ssmlVoiceGender = SsmlVoiceGender.FEMALE + .build(); + + // Select the type of audio file you want returned + AudioConfig audioConfig = + AudioConfig.newBuilder() + .setAudioEncoding(AudioEncoding.MP3) // MP3 audio. + .build(); + + // Perform the text-to-speech request + SynthesizeSpeechResponse response = + textToSpeechClient.synthesizeSpeech(input, voice, audioConfig); + + // Get the audio contents from the response + ByteString audioContents = response.getAudioContent(); + + // Write the response to the output file. + try (OutputStream out = new FileOutputStream("output.mp3")) { + out.write(audioContents.toByteArray()); + System.out.println("Audio content written to file \"output.mp3\""); + } + } + } + // [END tts_synthesize_text_file] + + // [START tts_synthesize_ssml_file] + /** + * Demonstrates using the Text to Speech client to synthesize a text file or ssml file. + * + * @param ssmlFile the ssml document to be synthesized. (e.g., hello.ssml) + * @throws Exception on TextToSpeechClient Errors. + */ + public static void synthesizeSsmlFile(String ssmlFile) throws Exception { + // Instantiates a client + try (TextToSpeechClient textToSpeechClient = TextToSpeechClient.create()) { + // Read the file's contents + String contents = new String(Files.readAllBytes(Paths.get(ssmlFile))); + // Set the ssml input to be synthesized + SynthesisInput input = SynthesisInput.newBuilder().setSsml(contents).build(); + + // Build the voice request + VoiceSelectionParams voice = + VoiceSelectionParams.newBuilder() + .setLanguageCode("en-US") // languageCode = "en_us" + .setSsmlGender(SsmlVoiceGender.FEMALE) // ssmlVoiceGender = SsmlVoiceGender.FEMALE + .build(); + + // Select the type of audio file you want returned + AudioConfig audioConfig = + AudioConfig.newBuilder() + .setAudioEncoding(AudioEncoding.MP3) // MP3 audio. + .build(); + + // Perform the text-to-speech request + SynthesizeSpeechResponse response = + textToSpeechClient.synthesizeSpeech(input, voice, audioConfig); + + // Get the audio contents from the response + ByteString audioContents = response.getAudioContent(); + + // Write the response to the output file. + try (OutputStream out = new FileOutputStream("output.mp3")) { + out.write(audioContents.toByteArray()); + System.out.println("Audio content written to file \"output.mp3\""); + } + } + } + // [END tts_synthesize_ssml_file] + + public static void main(String... args) throws Exception { + ArgumentParser parser = + ArgumentParsers.newFor("SynthesizeFile") + .build() + .defaultHelp(true) + .description("Synthesize a text file or ssml file."); + MutuallyExclusiveGroup group = parser.addMutuallyExclusiveGroup().required(true); + group.addArgument("--text").help("The text file from which to synthesize speech."); + group.addArgument("--ssml").help("The ssml file from which to synthesize speech."); + + try { + Namespace namespace = parser.parseArgs(args); + + if (namespace.get("text") != null) { + synthesizeTextFile(namespace.getString("text")); + } else { + synthesizeSsmlFile(namespace.getString("ssml")); + } + } catch (ArgumentParserException e) { + parser.handleError(e); + } + } +} diff --git a/texttospeech/snippets/src/main/java/com/example/texttospeech/SynthesizeText.java b/texttospeech/snippets/src/main/java/com/example/texttospeech/SynthesizeText.java new file mode 100644 index 00000000000..9e5f00484dc --- /dev/null +++ b/texttospeech/snippets/src/main/java/com/example/texttospeech/SynthesizeText.java @@ -0,0 +1,172 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.texttospeech; + +// Imports the Google Cloud client library +import com.google.cloud.texttospeech.v1.AudioConfig; +import com.google.cloud.texttospeech.v1.AudioEncoding; +import com.google.cloud.texttospeech.v1.SsmlVoiceGender; +import com.google.cloud.texttospeech.v1.SynthesisInput; +import com.google.cloud.texttospeech.v1.SynthesizeSpeechResponse; +import com.google.cloud.texttospeech.v1.TextToSpeechClient; +import com.google.cloud.texttospeech.v1.VoiceSelectionParams; +import com.google.protobuf.ByteString; +import java.io.FileOutputStream; +import java.io.OutputStream; + +/** + * Google Cloud TextToSpeech API sample application. Example usage: mvn package exec:java + * -Dexec.mainClass='com.example.texttospeech.SynthesizeText' -Dexec.args='--text "hello"' + */ +public class SynthesizeText { + + // [START tts_synthesize_text] + /** + * Demonstrates using the Text to Speech client to synthesize text or ssml. + * + * @param text the raw text to be synthesized. (e.g., "Hello there!") + * @throws Exception on TextToSpeechClient Errors. + */ + public static ByteString synthesizeText(String text) throws Exception { + // Instantiates a client + try (TextToSpeechClient textToSpeechClient = TextToSpeechClient.create()) { + // Set the text input to be synthesized + SynthesisInput input = SynthesisInput.newBuilder().setText(text).build(); + + // Build the voice request + VoiceSelectionParams voice = + VoiceSelectionParams.newBuilder() + .setLanguageCode("en-US") // languageCode = "en_us" + .setSsmlGender(SsmlVoiceGender.FEMALE) // ssmlVoiceGender = SsmlVoiceGender.FEMALE + .build(); + + // Select the type of audio file you want returned + AudioConfig audioConfig = + AudioConfig.newBuilder() + .setAudioEncoding(AudioEncoding.MP3) // MP3 audio. + .build(); + + // Perform the text-to-speech request + SynthesizeSpeechResponse response = + textToSpeechClient.synthesizeSpeech(input, voice, audioConfig); + + // Get the audio contents from the response + ByteString audioContents = response.getAudioContent(); + + // Write the response to the output file. + try (OutputStream out = new FileOutputStream("output.mp3")) { + out.write(audioContents.toByteArray()); + System.out.println("Audio content written to file \"output.mp3\""); + return audioContents; + } + } + } + // [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 + * + * @param text the raw text to be synthesized. (e.g., "Hello there!") + * @param effectsProfile audio profile to be used for synthesis. (e.g., + * "telephony-class-application") + * @throws Exception on TextToSpeechClient Errors. + */ + public static ByteString synthesizeTextWithAudioProfile(String text, String effectsProfile) + throws Exception { + // Instantiates a client + try (TextToSpeechClient textToSpeechClient = TextToSpeechClient.create()) { + // Set the text input to be synthesized + SynthesisInput input = SynthesisInput.newBuilder().setText(text).build(); + + // Build the voice request + VoiceSelectionParams voice = + VoiceSelectionParams.newBuilder() + .setLanguageCode("en-US") // languageCode = "en_us" + .setSsmlGender(SsmlVoiceGender.FEMALE) // ssmlVoiceGender = SsmlVoiceGender.FEMALE + .build(); + + // Select the type of audio file you want returned and the audio profile + AudioConfig audioConfig = + AudioConfig.newBuilder() + .setAudioEncoding(AudioEncoding.MP3) // MP3 audio. + .addEffectsProfileId(effectsProfile) // audio profile + .build(); + + // Perform the text-to-speech request + SynthesizeSpeechResponse response = + textToSpeechClient.synthesizeSpeech(input, voice, audioConfig); + + // Get the audio contents from the response + ByteString audioContents = response.getAudioContent(); + + // Write the response to the output file. + try (OutputStream out = new FileOutputStream("output.mp3")) { + out.write(audioContents.toByteArray()); + System.out.println("Audio content written to file \"output.mp3\""); + return audioContents; + } + } + } + // [END tts_synthesize_text_audio_profile] + + // [START tts_synthesize_ssml] + /** + * Demonstrates using the Text to Speech client to synthesize text or ssml. + * + *

Note: ssml must be well-formed according to: (https://www.w3.org/TR/speech-synthesis/ + * Example: Hello there. + * + * @param ssml the ssml document to be synthesized. (e.g., "Note: ssml must be well-formed according to: (https://www.w3.org/TR/speech-synthesis/ + * Example: Hello there. + * + * @param ssml the ssml document to be synthesized. (e.g., " voices = listAllSupportedVoices.listAllSupportedVoices(); + + // Assert + assertThat(voices.isEmpty()).isFalse(); + String got = bout.toString(); + assertThat(got).contains("en-US"); + assertThat(got).contains("SSML Voice Gender: MALE"); + assertThat(got).contains("SSML Voice Gender: FEMALE"); + } +} diff --git a/texttospeech/snippets/src/test/java/com/example/texttospeech/SsmlAddressesIT.java b/texttospeech/snippets/src/test/java/com/example/texttospeech/SsmlAddressesIT.java new file mode 100644 index 00000000000..cbe8ec85c12 --- /dev/null +++ b/texttospeech/snippets/src/test/java/com/example/texttospeech/SsmlAddressesIT.java @@ -0,0 +1,76 @@ +/* + * Copyright 2019 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.texttospeech; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for SsmlAddresses sample. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class SsmlAddressesIT { + + private static String OUTPUT = "output.mp3"; + private static String TEXT_FILE = "resources/example.txt"; + private static String SSML_FILE = "resources/example.ssml"; + + private ByteArrayOutputStream bout; + private PrintStream out; + private File outputFile; + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @Test + public void testTextToSsml() throws Exception { + // Act + String ssml = SsmlAddresses.textToSsml(TEXT_FILE); + String expectedSsml = new String(Files.readAllBytes(Paths.get(SSML_FILE))); + + // Assert + assertThat(ssml).contains(expectedSsml); + } + + @Test + public void testSsmlToAudio() throws Exception { + // Act + String ssml = new String(Files.readAllBytes(Paths.get(SSML_FILE))); + SsmlAddresses.ssmlToAudio(ssml, OUTPUT); + + // Assert + outputFile = new File(OUTPUT); + assertThat(outputFile.isFile()).isTrue(); + String got = bout.toString(); + assertThat(got).contains("Audio content written to file output.mp3"); + + // After + outputFile.delete(); + } +} diff --git a/texttospeech/snippets/src/test/java/com/example/texttospeech/SynthesizeFileBetaIT.java b/texttospeech/snippets/src/test/java/com/example/texttospeech/SynthesizeFileBetaIT.java new file mode 100644 index 00000000000..590dacc5e4f --- /dev/null +++ b/texttospeech/snippets/src/test/java/com/example/texttospeech/SynthesizeFileBetaIT.java @@ -0,0 +1,78 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.texttospeech; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for SynthesizeFileBeta sample. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class SynthesizeFileBetaIT { + + private static String OUTPUT = "output.mp3"; + private static String TEXT_FILE = "resources/hello.txt"; + private static String SSML_FILE = "resources/hello.ssml"; + + private ByteArrayOutputStream bout; + private PrintStream out; + private File outputFile; + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() { + outputFile.delete(); + } + + @Test + public void testSynthesizeText() throws Exception { + // Act + SynthesizeFileBeta.synthesizeTextFile(TEXT_FILE); + + // Assert + outputFile = new File(OUTPUT); + assertThat(outputFile.isFile()).isTrue(); + String got = bout.toString(); + assertThat(got).contains("Audio content written to file \"output.mp3\""); + } + + @Test + public void testSynthesizeSsml() throws Exception { + // Act + SynthesizeFileBeta.synthesizeSsmlFile(SSML_FILE); + + // Assert + outputFile = new File(OUTPUT); + assertThat(outputFile.isFile()).isTrue(); + String got = bout.toString(); + assertThat(got).contains("Audio content written to file \"output.mp3\""); + } +} diff --git a/texttospeech/snippets/src/test/java/com/example/texttospeech/SynthesizeFileIT.java b/texttospeech/snippets/src/test/java/com/example/texttospeech/SynthesizeFileIT.java new file mode 100644 index 00000000000..1737d220a5d --- /dev/null +++ b/texttospeech/snippets/src/test/java/com/example/texttospeech/SynthesizeFileIT.java @@ -0,0 +1,81 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.texttospeech; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.protobuf.ByteString; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for SynthesizeFile sample. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class SynthesizeFileIT { + + private static String OUTPUT = "output.mp3"; + private static String TEXT_FILE = "resources/hello.txt"; + private static String SSML_FILE = "resources/hello.ssml"; + + private ByteArrayOutputStream bout; + private PrintStream out; + private File outputFile; + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() { + outputFile.delete(); + } + + @Test + public void testSynthesizeText() throws Exception { + // Act + ByteString audioContents = SynthesizeFile.synthesizeTextFile(TEXT_FILE); + + // Assert + assertThat(audioContents.isEmpty()).isFalse(); + outputFile = new File(OUTPUT); + assertThat(outputFile.isFile()).isTrue(); + String got = bout.toString(); + assertThat(got).contains("Audio content written to file \"output.mp3\""); + } + + @Test + public void testSynthesizeSsml() throws Exception { + // Act + ByteString audioContents = SynthesizeFile.synthesizeSsmlFile(SSML_FILE); + + // Assert + assertThat(audioContents.isEmpty()).isFalse(); + outputFile = new File(OUTPUT); + assertThat(outputFile.isFile()).isTrue(); + String got = bout.toString(); + assertThat(got).contains("Audio content written to file \"output.mp3\""); + } +} diff --git a/texttospeech/snippets/src/test/java/com/example/texttospeech/SynthesizeTextBetaIT.java b/texttospeech/snippets/src/test/java/com/example/texttospeech/SynthesizeTextBetaIT.java new file mode 100644 index 00000000000..91fb87b431c --- /dev/null +++ b/texttospeech/snippets/src/test/java/com/example/texttospeech/SynthesizeTextBetaIT.java @@ -0,0 +1,91 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.texttospeech; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for SynthesizeTextBeta sample. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class SynthesizeTextBetaIT { + + private static String OUTPUT = "output.mp3"; + private static String TEXT = "Hello there."; + private static String SSML = "Hello there."; + private static String EFFECTSPROFILE = "telephony-class-application"; + + private ByteArrayOutputStream bout; + private PrintStream out; + private File outputFile; + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() { + outputFile.delete(); + } + + @Test + public void testSynthesizeText() throws Exception { + // Act + SynthesizeTextBeta.synthesizeText(TEXT); + + // Assert + outputFile = new File(OUTPUT); + assertThat(outputFile.isFile()).isTrue(); + String got = bout.toString(); + assertThat(got).contains("Audio content written to file \"output.mp3\""); + } + + @Test + public void testSynthesizeSsml() throws Exception { + // Act + SynthesizeTextBeta.synthesizeSsml(SSML); + + // Assert + outputFile = new File(OUTPUT); + assertThat(outputFile.isFile()).isTrue(); + String got = bout.toString(); + assertThat(got).contains("Audio content written to file \"output.mp3\""); + } + + @Test + public void testSynthesizeTextWithAudioProfile() throws Exception { + // Act + SynthesizeTextBeta.synthesizeTextWithAudioProfile(TEXT, EFFECTSPROFILE); + + // Assert + outputFile = new File(OUTPUT); + assertThat(outputFile.isFile()).isTrue(); + String got = bout.toString(); + assertThat(got).contains("Audio content written to file \"output.mp3\""); + } +} diff --git a/texttospeech/snippets/src/test/java/com/example/texttospeech/SynthesizeTextIT.java b/texttospeech/snippets/src/test/java/com/example/texttospeech/SynthesizeTextIT.java new file mode 100644 index 00000000000..826052d7177 --- /dev/null +++ b/texttospeech/snippets/src/test/java/com/example/texttospeech/SynthesizeTextIT.java @@ -0,0 +1,95 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.texttospeech; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.protobuf.ByteString; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for SynthesizeText sample. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class SynthesizeTextIT { + + private static String OUTPUT = "output.mp3"; + private static String TEXT = "Hello there."; + private static String SSML = "Hello there."; + private static String EFFECTSPROFILE = "telephony-class-application"; + + private ByteArrayOutputStream bout; + private PrintStream out; + private File outputFile; + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() { + outputFile.delete(); + } + + @Test + public void testSynthesizeText() throws Exception { + // Act + ByteString audioContents = SynthesizeText.synthesizeText(TEXT); + + // Assert + assertThat(audioContents.isEmpty()).isFalse(); + outputFile = new File(OUTPUT); + assertThat(outputFile.isFile()).isTrue(); + String got = bout.toString(); + assertThat(got).contains("Audio content written to file \"output.mp3\""); + } + + @Test + public void testSynthesizeSsml() throws Exception { + // Act + ByteString audioContents = SynthesizeText.synthesizeSsml(SSML); + + // Assert + assertThat(audioContents.isEmpty()).isFalse(); + outputFile = new File(OUTPUT); + assertThat(outputFile.isFile()).isTrue(); + String got = bout.toString(); + assertThat(got).contains("Audio content written to file \"output.mp3\""); + } + + @Test + public void testSynthesizeTextWithAudioProfile() throws Exception { + // Act + ByteString audioContents = SynthesizeText.synthesizeTextWithAudioProfile(TEXT, EFFECTSPROFILE); + + // Assert + assertThat(audioContents.isEmpty()).isFalse(); + outputFile = new File(OUTPUT); + assertThat(outputFile.isFile()).isTrue(); + String got = bout.toString(); + assertThat(got).contains("Audio content written to file \"output.mp3\""); + } +} diff --git a/unittests/pom.xml b/unittests/pom.xml index 9734813186b..20df71a2dcf 100644 --- a/unittests/pom.xml +++ b/unittests/pom.xml @@ -71,7 +71,7 @@ com.google.api-client google-api-client-appengine - 1.34.1 + 2.0.1 test diff --git a/video/README.md b/video/README.md index 5975d3aeb6b..03595140697 100644 --- a/video/README.md +++ b/video/README.md @@ -1,3 +1,36 @@ -# Video Intelligence Samples have been moved +# [Cloud Video Intelligence: Java Samples](https://github.com/GoogleCloudPlatform/java-docs-samples/tree/main/video) -[https://github.com/googleapis/java-video-intelligence](https://github.com/googleapis/java-video-intelligence/tree/main/samples). \ No newline at end of file +[![Open in Cloud Shell][shell_img]][shell_link] + + + +## Table of Contents + +* [Build the sample](#build-the-sample) +* [Samples](#samples) + + +## Build the sample + +Install [Maven](http://maven.apache.org/). + +Build your project with: + +``` +mvn clean package -DskipTests=True +``` + +## Samples + +Please follow [Before you begin](https://cloud.google.com/video-intelligence/docs/annotate-video-client-libraries#before-you-begin) for project and auth setup before you run the samples. + + +## Run +Run all tests: +``` +mvn clean verify +``` + +[shell_img]: https://gstatic.com/cloudssh/images/open-btn.png +[shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/java-docs-samples&page=editor&open_in_editor=video/README.md +[product-docs]: https://cloud.google.com/video-intelligence/docs/ diff --git a/video/pom.xml b/video/pom.xml new file mode 100644 index 00000000000..161d75787b4 --- /dev/null +++ b/video/pom.xml @@ -0,0 +1,71 @@ + + + 4.0.0 + com.google.cloud + videointelligence-snippets + jar + Google Cloud Video Intelligence Snippets + https://github.com/GoogleCloudPlatform/java-docs-samples/tree/main/video + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + 1.8 + 1.8 + UTF-8 + + + + + + + + com.google.cloud + libraries-bom + 26.1.3 + pom + import + + + + + + + com.google.cloud + google-cloud-video-intelligence + + + com.google.cloud + google-cloud-storage + + + + + junit + junit + 4.13.2 + test + + + com.google.truth + truth + 1.1.3 + test + + + com.google.cloud + google-cloud-core + 2.8.22 + test + tests + + + diff --git a/video/resources/cat.mp4 b/video/resources/cat.mp4 new file mode 100644 index 00000000000..0e071b9ec67 Binary files /dev/null and b/video/resources/cat.mp4 differ diff --git a/video/resources/googlework_short.mp4 b/video/resources/googlework_short.mp4 new file mode 100644 index 00000000000..be0f40f8ad6 Binary files /dev/null and b/video/resources/googlework_short.mp4 differ diff --git a/video/src/main/java/beta/video/Detect.java b/video/src/main/java/beta/video/Detect.java new file mode 100644 index 00000000000..e87cdb147d4 --- /dev/null +++ b/video/src/main/java/beta/video/Detect.java @@ -0,0 +1,139 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.video; + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.videointelligence.v1p1beta1.AnnotateVideoProgress; +import com.google.cloud.videointelligence.v1p1beta1.AnnotateVideoRequest; +import com.google.cloud.videointelligence.v1p1beta1.AnnotateVideoResponse; +import com.google.cloud.videointelligence.v1p1beta1.Feature; +import com.google.cloud.videointelligence.v1p1beta1.SpeechRecognitionAlternative; +import com.google.cloud.videointelligence.v1p1beta1.SpeechTranscription; +import com.google.cloud.videointelligence.v1p1beta1.SpeechTranscriptionConfig; +import com.google.cloud.videointelligence.v1p1beta1.VideoAnnotationResults; +import com.google.cloud.videointelligence.v1p1beta1.VideoContext; +import com.google.cloud.videointelligence.v1p1beta1.VideoIntelligenceServiceClient; +import com.google.cloud.videointelligence.v1p1beta1.WordInfo; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +public class Detect { + /** + * Detects video transcription using the Video Intelligence API + * + * @param args specifies features to detect and the path to the video on Google Cloud Storage. + */ + public static void main(String[] args) { + try { + argsHelper(args); + } catch (Exception e) { + System.out.println("Exception while running:\n" + e.getMessage() + "\n"); + e.printStackTrace(System.out); + } + } + + /** + * Helper that handles the input passed to the program. + * + * @param args specifies features to detect and the path to the video on Google Cloud Storage. + * @throws IOException on Input/Output errors. + */ + public static void argsHelper(String[] args) throws Exception { + if (args.length < 1) { + System.out.println("Usage:"); + System.out.printf( + "\tjava %s \"\" \"\"\n" + + "Commands:\n" + + "\tspeech-transcription\n" + + "Path:\n\tA URI for a Cloud Storage resource (gs://...)\n" + + "Examples: ", + Detect.class.getCanonicalName()); + return; + } + String command = args[0]; + String path = args.length > 1 ? args[1] : ""; + + if (command.equals("speech-transcription")) { + speechTranscription(path); + } + } + + // [START video_speech_transcription_gcs_beta] + /** + * Transcribe speech from a video stored on GCS. + * + * @param gcsUri the path to the video file to analyze. + */ + public static void speechTranscription(String gcsUri) throws Exception { + // Instantiate a com.google.cloud.videointelligence.v1p1beta1.VideoIntelligenceServiceClient + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Set the language code + SpeechTranscriptionConfig config = + SpeechTranscriptionConfig.newBuilder() + .setLanguageCode("en-US") + .setEnableAutomaticPunctuation(true) + .build(); + + // Set the video context with the above configuration + VideoContext context = VideoContext.newBuilder().setSpeechTranscriptionConfig(config).build(); + + // Create the request + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputUri(gcsUri) + .addFeatures(Feature.SPEECH_TRANSCRIPTION) + .setVideoContext(context) + .build(); + + // asynchronously perform speech transcription on videos + OperationFuture response = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + // Display the results + for (VideoAnnotationResults results : + response.get(300, TimeUnit.SECONDS).getAnnotationResultsList()) { + for (SpeechTranscription speechTranscription : results.getSpeechTranscriptionsList()) { + try { + // Print the transcription + if (speechTranscription.getAlternativesCount() > 0) { + SpeechRecognitionAlternative alternative = speechTranscription.getAlternatives(0); + + System.out.printf("Transcript: %s\n", alternative.getTranscript()); + System.out.printf("Confidence: %.2f\n", alternative.getConfidence()); + + System.out.println("Word level information:"); + for (WordInfo wordInfo : alternative.getWordsList()) { + double startTime = + wordInfo.getStartTime().getSeconds() + wordInfo.getStartTime().getNanos() / 1e9; + double endTime = + wordInfo.getEndTime().getSeconds() + wordInfo.getEndTime().getNanos() / 1e9; + System.out.printf( + "\t%4.2fs - %4.2fs: %s\n", startTime, endTime, wordInfo.getWord()); + } + } else { + System.out.println("No transcription found"); + } + } catch (IndexOutOfBoundsException ioe) { + System.out.println("Could not retrieve frame: " + ioe.getMessage()); + } + } + } + } + } + // [END video_speech_transcription_gcs_beta] +} diff --git a/video/src/main/java/beta/video/DetectLogo.java b/video/src/main/java/beta/video/DetectLogo.java new file mode 100644 index 00000000000..4df53415318 --- /dev/null +++ b/video/src/main/java/beta/video/DetectLogo.java @@ -0,0 +1,146 @@ +/* + * 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.video; + +// [START video_detect_logo_beta] + +import com.google.cloud.videointelligence.v1p3beta1.AnnotateVideoRequest; +import com.google.cloud.videointelligence.v1p3beta1.AnnotateVideoResponse; +import com.google.cloud.videointelligence.v1p3beta1.DetectedAttribute; +import com.google.cloud.videointelligence.v1p3beta1.Entity; +import com.google.cloud.videointelligence.v1p3beta1.Feature; +import com.google.cloud.videointelligence.v1p3beta1.LogoRecognitionAnnotation; +import com.google.cloud.videointelligence.v1p3beta1.NormalizedBoundingBox; +import com.google.cloud.videointelligence.v1p3beta1.TimestampedObject; +import com.google.cloud.videointelligence.v1p3beta1.Track; +import com.google.cloud.videointelligence.v1p3beta1.VideoAnnotationResults; +import com.google.cloud.videointelligence.v1p3beta1.VideoIntelligenceServiceClient; +import com.google.cloud.videointelligence.v1p3beta1.VideoSegment; +import com.google.protobuf.ByteString; +import com.google.protobuf.Duration; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.ExecutionException; + +public class DetectLogo { + + public void detectLogo() throws IOException, ExecutionException, InterruptedException { + String filePath = "path/to/your/video.mp4"; + detectLogo(filePath); + } + + public static void detectLogo(String localFilePath) + 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 (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Read the files contents + Path path = Paths.get(localFilePath); + byte[] data = Files.readAllBytes(path); + ByteString inputContent = ByteString.copyFrom(data); + + // Build the request with the inputContent and set the Feature + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputContent(inputContent) + .addFeatures(Feature.LOGO_RECOGNITION) + .build(); + + // Make the asynchronous request + AnnotateVideoResponse response = client.annotateVideoAsync(request).get(); + + // Get the first response, since we sent only one video. + VideoAnnotationResults annotationResult = response.getAnnotationResultsList().get(0); + + // Annotations for list of logos detected, tracked and recognized in the video. + for (LogoRecognitionAnnotation logoRecognitionAnnotation : + annotationResult.getLogoRecognitionAnnotationsList()) { + + Entity entity = logoRecognitionAnnotation.getEntity(); + // Opaque entity ID. Some IDs may be available in [Google Knowledge Graph Search + // API](https://developers.google.com/knowledge-graph/). + System.out.printf("Entity Id: %s\n", entity.getEntityId()); + System.out.printf("Description: %s\n", entity.getDescription()); + + // All logo tracks where the recognized logo appears. Each track corresponds to one logo + // instance appearing in consecutive frames. + for (Track track : logoRecognitionAnnotation.getTracksList()) { + + // Video segment of a track. + VideoSegment segment = track.getSegment(); + Duration segmentStartTimeOffset = segment.getStartTimeOffset(); + System.out.printf( + "\n\tStart Time Offset: %s.%s\n", + segmentStartTimeOffset.getSeconds(), segmentStartTimeOffset.getNanos()); + Duration segmentEndTimeOffset = segment.getEndTimeOffset(); + System.out.printf( + "\tEnd Time Offset: %s.%s\n", + segmentEndTimeOffset.getSeconds(), segmentEndTimeOffset.getNanos()); + System.out.printf("\tConfidence: %s\n", track.getConfidence()); + + // The object with timestamp and attributes per frame in the track. + for (TimestampedObject timestampedObject : track.getTimestampedObjectsList()) { + + // Normalized Bounding box in a frame, where the object is located. + NormalizedBoundingBox normalizedBoundingBox = + timestampedObject.getNormalizedBoundingBox(); + System.out.printf("\n\t\tLeft: %s\n", normalizedBoundingBox.getLeft()); + System.out.printf("\t\tTop: %s\n", normalizedBoundingBox.getTop()); + System.out.printf("\t\tRight: %s\n", normalizedBoundingBox.getRight()); + System.out.printf("\t\tBottom: %s\n", normalizedBoundingBox.getBottom()); + + // Optional. The attributes of the object in the bounding box. + for (DetectedAttribute attribute : timestampedObject.getAttributesList()) { + System.out.printf("\n\t\t\tName: %s\n", attribute.getName()); + System.out.printf("\t\t\tConfidence: %s\n", attribute.getConfidence()); + System.out.printf("\t\t\tValue: %s\n", attribute.getValue()); + } + } + + // Optional. Attributes in the track level. + for (DetectedAttribute trackAttribute : track.getAttributesList()) { + System.out.printf("\n\t\tName : %s\n", trackAttribute.getName()); + System.out.printf("\t\tConfidence : %s\n", trackAttribute.getConfidence()); + System.out.printf("\t\tValue : %s\n", trackAttribute.getValue()); + } + } + + // All video segments where the recognized logo appears. There might be multiple instances + // of the same logo class appearing in one VideoSegment. + for (VideoSegment logoRecognitionAnnotationSegment : + logoRecognitionAnnotation.getSegmentsList()) { + Duration logoRecognitionAnnotationSegmentStartTimeOffset = + logoRecognitionAnnotationSegment.getStartTimeOffset(); + System.out.printf( + "\n\tStart Time Offset : %s.%s\n", + logoRecognitionAnnotationSegmentStartTimeOffset.getSeconds(), + logoRecognitionAnnotationSegmentStartTimeOffset.getNanos()); + Duration logoRecognitionAnnotationSegmentEndTimeOffset = + logoRecognitionAnnotationSegment.getEndTimeOffset(); + System.out.printf( + "\tEnd Time Offset : %s.%s\n", + logoRecognitionAnnotationSegmentEndTimeOffset.getSeconds(), + logoRecognitionAnnotationSegmentEndTimeOffset.getNanos()); + } + } + } + } +} +// [END video_detect_logo_beta] diff --git a/video/src/main/java/beta/video/DetectLogoGcs.java b/video/src/main/java/beta/video/DetectLogoGcs.java new file mode 100644 index 00000000000..ee8fd0e5ce7 --- /dev/null +++ b/video/src/main/java/beta/video/DetectLogoGcs.java @@ -0,0 +1,137 @@ +/* + * 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.video; + +// [START video_detect_logo_gcs_beta] + +import com.google.cloud.videointelligence.v1p3beta1.AnnotateVideoRequest; +import com.google.cloud.videointelligence.v1p3beta1.AnnotateVideoResponse; +import com.google.cloud.videointelligence.v1p3beta1.DetectedAttribute; +import com.google.cloud.videointelligence.v1p3beta1.Entity; +import com.google.cloud.videointelligence.v1p3beta1.Feature; +import com.google.cloud.videointelligence.v1p3beta1.LogoRecognitionAnnotation; +import com.google.cloud.videointelligence.v1p3beta1.NormalizedBoundingBox; +import com.google.cloud.videointelligence.v1p3beta1.TimestampedObject; +import com.google.cloud.videointelligence.v1p3beta1.Track; +import com.google.cloud.videointelligence.v1p3beta1.VideoAnnotationResults; +import com.google.cloud.videointelligence.v1p3beta1.VideoIntelligenceServiceClient; +import com.google.cloud.videointelligence.v1p3beta1.VideoSegment; +import com.google.protobuf.Duration; +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +public class DetectLogoGcs { + + public void detectLogo() throws IOException, ExecutionException, InterruptedException { + String inputUri = "gs://cloud-samples-data/video/googlework_short.mp4"; + detectLogoGcs(inputUri); + } + + public static void detectLogoGcs(String inputUri) + 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 (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Build the request with the inputUri and set the Feature + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputUri(inputUri) + .addFeatures(Feature.LOGO_RECOGNITION) + .build(); + + // Make the asynchronous request + AnnotateVideoResponse response = client.annotateVideoAsync(request).get(); + + // Get the first response, since we sent only one video. + VideoAnnotationResults annotationResult = response.getAnnotationResultsList().get(0); + + // Annotations for list of logos detected, tracked and recognized in the video. + for (LogoRecognitionAnnotation logoRecognitionAnnotation : + annotationResult.getLogoRecognitionAnnotationsList()) { + + Entity entity = logoRecognitionAnnotation.getEntity(); + // Opaque entity ID. Some IDs may be available in [Google Knowledge Graph Search + // API](https://developers.google.com/knowledge-graph/). + System.out.printf("Entity Id: %s\n", entity.getEntityId()); + System.out.printf("Description: %s\n", entity.getDescription()); + + // All logo tracks where the recognized logo appears. Each track corresponds to one logo + // instance appearing in consecutive frames. + for (Track track : logoRecognitionAnnotation.getTracksList()) { + + // Video segment of a track. + VideoSegment segment = track.getSegment(); + Duration segmentStartTimeOffset = segment.getStartTimeOffset(); + System.out.printf( + "\n\tStart Time Offset: %s.%s\n", + segmentStartTimeOffset.getSeconds(), segmentStartTimeOffset.getNanos()); + Duration segmentEndTimeOffset = segment.getEndTimeOffset(); + System.out.printf( + "\tEnd Time Offset: %s.%s\n", + segmentEndTimeOffset.getSeconds(), segmentEndTimeOffset.getNanos()); + System.out.printf("\tConfidence: %s\n", track.getConfidence()); + + // The object with timestamp and attributes per frame in the track. + for (TimestampedObject timestampedObject : track.getTimestampedObjectsList()) { + + // Normalized Bounding box in a frame, where the object is located. + NormalizedBoundingBox normalizedBoundingBox = + timestampedObject.getNormalizedBoundingBox(); + System.out.printf("\n\t\tLeft: %s\n", normalizedBoundingBox.getLeft()); + System.out.printf("\t\tTop: %s\n", normalizedBoundingBox.getTop()); + System.out.printf("\t\tRight: %s\n", normalizedBoundingBox.getRight()); + System.out.printf("\t\tBottom: %s\n", normalizedBoundingBox.getBottom()); + + // Optional. The attributes of the object in the bounding box. + for (DetectedAttribute attribute : timestampedObject.getAttributesList()) { + System.out.printf("\n\t\t\tName: %s\n", attribute.getName()); + System.out.printf("\t\t\tConfidence: %s\n", attribute.getConfidence()); + System.out.printf("\t\t\tValue: %s\n", attribute.getValue()); + } + } + + // Optional. Attributes in the track level. + for (DetectedAttribute trackAttribute : track.getAttributesList()) { + System.out.printf("\n\t\tName : %s\n", trackAttribute.getName()); + System.out.printf("\t\tConfidence : %s\n", trackAttribute.getConfidence()); + System.out.printf("\t\tValue : %s\n", trackAttribute.getValue()); + } + } + + // All video segments where the recognized logo appears. There might be multiple instances + // of the same logo class appearing in one VideoSegment. + for (VideoSegment logoRecognitionAnnotationSegment : + logoRecognitionAnnotation.getSegmentsList()) { + Duration logoRecognitionAnnotationSegmentStartTimeOffset = + logoRecognitionAnnotationSegment.getStartTimeOffset(); + System.out.printf( + "\n\tStart Time Offset : %s.%s\n", + logoRecognitionAnnotationSegmentStartTimeOffset.getSeconds(), + logoRecognitionAnnotationSegmentStartTimeOffset.getNanos()); + Duration logoRecognitionAnnotationSegmentEndTimeOffset = + logoRecognitionAnnotationSegment.getEndTimeOffset(); + System.out.printf( + "\tEnd Time Offset : %s.%s\n", + logoRecognitionAnnotationSegmentEndTimeOffset.getSeconds(), + logoRecognitionAnnotationSegmentEndTimeOffset.getNanos()); + } + } + } + } +} +// [END video_detect_logo_gcs_beta] diff --git a/video/src/main/java/beta/video/StreamingAnnotationToStorage.java b/video/src/main/java/beta/video/StreamingAnnotationToStorage.java new file mode 100644 index 00000000000..ab4834f5b50 --- /dev/null +++ b/video/src/main/java/beta/video/StreamingAnnotationToStorage.java @@ -0,0 +1,98 @@ +/* + * 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.video; + +// [START video_streaming_annotation_to_storage_beta] + +import com.google.api.gax.rpc.BidiStream; +import com.google.cloud.videointelligence.v1p3beta1.StreamingAnnotateVideoRequest; +import com.google.cloud.videointelligence.v1p3beta1.StreamingAnnotateVideoResponse; +import com.google.cloud.videointelligence.v1p3beta1.StreamingFeature; +import com.google.cloud.videointelligence.v1p3beta1.StreamingLabelDetectionConfig; +import com.google.cloud.videointelligence.v1p3beta1.StreamingStorageConfig; +import com.google.cloud.videointelligence.v1p3beta1.StreamingVideoConfig; +import com.google.cloud.videointelligence.v1p3beta1.StreamingVideoIntelligenceServiceClient; +import com.google.protobuf.ByteString; +import io.grpc.StatusRuntimeException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.concurrent.TimeoutException; + +public class StreamingAnnotationToStorage { + + // Perform streaming video detection for explicit content + static void streamingAnnotationToStorage(String filePath, String gcsUri) + throws IOException, TimeoutException, StatusRuntimeException { + // String filePath = "path_to_your_video_file"; + // String gcsUri = "gs://BUCKET_ID"; + + try (StreamingVideoIntelligenceServiceClient client = + StreamingVideoIntelligenceServiceClient.create()) { + + Path path = Paths.get(filePath); + byte[] data = Files.readAllBytes(path); + // Set the chunk size to 5MB (recommended less than 10MB). + int chunkSize = 5 * 1024 * 1024; + int numChunks = (int) Math.ceil((double) data.length / chunkSize); + + StreamingStorageConfig streamingStorageConfig = + StreamingStorageConfig.newBuilder() + .setEnableStorageAnnotationResult(true) + .setAnnotationResultStorageDirectory(gcsUri) + .build(); + + StreamingLabelDetectionConfig labelConfig = + StreamingLabelDetectionConfig.newBuilder().setStationaryCamera(false).build(); + + StreamingVideoConfig streamingVideoConfig = + StreamingVideoConfig.newBuilder() + .setFeature(StreamingFeature.STREAMING_LABEL_DETECTION) + .setLabelDetectionConfig(labelConfig) + .setStorageConfig(streamingStorageConfig) + .build(); + + BidiStream call = + client.streamingAnnotateVideoCallable().call(); + + // The first request must **only** contain the audio configuration: + call.send( + StreamingAnnotateVideoRequest.newBuilder().setVideoConfig(streamingVideoConfig).build()); + + // Subsequent requests must **only** contain the audio data. + // Send the requests in chunks + for (int i = 0; i < numChunks; i++) { + call.send( + StreamingAnnotateVideoRequest.newBuilder() + .setInputContent( + ByteString.copyFrom( + Arrays.copyOfRange(data, i * chunkSize, i * chunkSize + chunkSize))) + .build()); + } + + // Tell the service you are done sending data + call.closeSend(); + + for (StreamingAnnotateVideoResponse response : call) { + System.out.format("Storage Uri: %s\n", response.getAnnotationResultsUri()); + } + } + } +} +// [END video_streaming_annotation_to_storage_beta] diff --git a/video/src/main/java/beta/video/StreamingAutoMlActionRecognition.java b/video/src/main/java/beta/video/StreamingAutoMlActionRecognition.java new file mode 100644 index 00000000000..c06ff324cc0 --- /dev/null +++ b/video/src/main/java/beta/video/StreamingAutoMlActionRecognition.java @@ -0,0 +1,114 @@ +/* + * 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.video; + +// [START video_streaming_automl_action_recognition_beta] + +import com.google.api.gax.rpc.BidiStream; +import com.google.cloud.videointelligence.v1p3beta1.LabelAnnotation; +import com.google.cloud.videointelligence.v1p3beta1.LabelFrame; +import com.google.cloud.videointelligence.v1p3beta1.StreamingAnnotateVideoRequest; +import com.google.cloud.videointelligence.v1p3beta1.StreamingAnnotateVideoResponse; +import com.google.cloud.videointelligence.v1p3beta1.StreamingAutomlActionRecognitionConfig; +import com.google.cloud.videointelligence.v1p3beta1.StreamingFeature; +import com.google.cloud.videointelligence.v1p3beta1.StreamingVideoAnnotationResults; +import com.google.cloud.videointelligence.v1p3beta1.StreamingVideoConfig; +import com.google.cloud.videointelligence.v1p3beta1.StreamingVideoIntelligenceServiceClient; +import com.google.protobuf.ByteString; +import io.grpc.StatusRuntimeException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.concurrent.TimeoutException; + +class StreamingAutoMlActionRecognition { + + // Perform streaming video action recognition + static void streamingAutoMlActionRecognition(String filePath, String projectId, String modelId) + throws IOException, TimeoutException, StatusRuntimeException { + + try (StreamingVideoIntelligenceServiceClient client = + StreamingVideoIntelligenceServiceClient.create()) { + + Path path = Paths.get(filePath); + byte[] data = Files.readAllBytes(path); + // Set the chunk size to 5MB (recommended less than 10MB). + int chunkSize = 5 * 1024 * 1024; + int numChunks = (int) Math.ceil((double) data.length / chunkSize); + + String modelPath = + String.format("projects/%s/locations/us-central1/models/%s", projectId, modelId); + + System.out.println(modelPath); + + StreamingAutomlActionRecognitionConfig streamingAutomlActionRecognitionConfig = + StreamingAutomlActionRecognitionConfig.newBuilder().setModelName(modelPath).build(); + + StreamingVideoConfig streamingVideoConfig = + StreamingVideoConfig.newBuilder() + .setFeature(StreamingFeature.STREAMING_AUTOML_ACTION_RECOGNITION) + .setAutomlActionRecognitionConfig(streamingAutomlActionRecognitionConfig) + .build(); + + BidiStream call = + client.streamingAnnotateVideoCallable().call(); + + // The first request must **only** contain the video configuration: + call.send( + StreamingAnnotateVideoRequest.newBuilder().setVideoConfig(streamingVideoConfig).build()); + + // Subsequent requests must **only** contain the video data. + // Send the requests in chunks + for (int i = 0; i < numChunks; i++) { + call.send( + StreamingAnnotateVideoRequest.newBuilder() + .setInputContent( + ByteString.copyFrom( + Arrays.copyOfRange(data, i * chunkSize, i * chunkSize + chunkSize))) + .build()); + } + + // Tell the service you are done sending data + call.closeSend(); + + for (StreamingAnnotateVideoResponse response : call) { + if (response.hasError()) { + System.out.println(response.getError().getMessage()); + break; + } + + StreamingVideoAnnotationResults annotationResults = response.getAnnotationResults(); + + for (LabelAnnotation annotation : annotationResults.getLabelAnnotationsList()) { + String entity = annotation.getEntity().getDescription(); + + // There is only one frame per annotation + LabelFrame labelFrame = annotation.getFrames(0); + double offset = + labelFrame.getTimeOffset().getSeconds() + labelFrame.getTimeOffset().getNanos() / 1e9; + float confidence = labelFrame.getConfidence(); + + System.out.format("At %fs segment: %s (%f)\n", offset, entity, confidence); + } + } + System.out.println("Video streamed successfully."); + } + } +} +// [END video_streaming_automl_action_recognition_beta] diff --git a/video/src/main/java/beta/video/StreamingAutoMlClassification.java b/video/src/main/java/beta/video/StreamingAutoMlClassification.java new file mode 100644 index 00000000000..5a01c581505 --- /dev/null +++ b/video/src/main/java/beta/video/StreamingAutoMlClassification.java @@ -0,0 +1,117 @@ +/* + * 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.video; + +// [START video_streaming_automl_classification_beta] + +import com.google.api.gax.rpc.BidiStream; +import com.google.cloud.videointelligence.v1p3beta1.LabelAnnotation; +import com.google.cloud.videointelligence.v1p3beta1.LabelFrame; +import com.google.cloud.videointelligence.v1p3beta1.StreamingAnnotateVideoRequest; +import com.google.cloud.videointelligence.v1p3beta1.StreamingAnnotateVideoResponse; +import com.google.cloud.videointelligence.v1p3beta1.StreamingAutomlClassificationConfig; +import com.google.cloud.videointelligence.v1p3beta1.StreamingFeature; +import com.google.cloud.videointelligence.v1p3beta1.StreamingVideoAnnotationResults; +import com.google.cloud.videointelligence.v1p3beta1.StreamingVideoConfig; +import com.google.cloud.videointelligence.v1p3beta1.StreamingVideoIntelligenceServiceClient; +import com.google.protobuf.ByteString; +import io.grpc.StatusRuntimeException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.concurrent.TimeoutException; + +class StreamingAutoMlClassification { + + // Perform streaming video classification with an AutoML Model + static void streamingAutoMlClassification(String filePath, String projectId, String modelId) + throws TimeoutException, StatusRuntimeException, IOException { + // String filePath = "path_to_your_video_file"; + // String projectId = "YOUR_GCP_PROJECT_ID"; + // String modelId = "YOUR_AUTO_ML_CLASSIFICATION_MODEL_ID"; + + try (StreamingVideoIntelligenceServiceClient client = + StreamingVideoIntelligenceServiceClient.create()) { + + Path path = Paths.get(filePath); + byte[] data = Files.readAllBytes(path); + // Set the chunk size to 5MB (recommended less than 10MB). + int chunkSize = 5 * 1024 * 1024; + int numChunks = (int) Math.ceil((double) data.length / chunkSize); + + String modelPath = + String.format("projects/%s/locations/us-central1/models/%s", projectId, modelId); + + System.out.println(modelPath); + + StreamingAutomlClassificationConfig streamingAutomlClassificationConfig = + StreamingAutomlClassificationConfig.newBuilder().setModelName(modelPath).build(); + + StreamingVideoConfig streamingVideoConfig = + StreamingVideoConfig.newBuilder() + .setFeature(StreamingFeature.STREAMING_AUTOML_CLASSIFICATION) + .setAutomlClassificationConfig(streamingAutomlClassificationConfig) + .build(); + + BidiStream call = + client.streamingAnnotateVideoCallable().call(); + + // The first request must **only** contain the audio configuration: + call.send( + StreamingAnnotateVideoRequest.newBuilder().setVideoConfig(streamingVideoConfig).build()); + + // Subsequent requests must **only** contain the audio data. + // Send the requests in chunks + for (int i = 0; i < numChunks; i++) { + call.send( + StreamingAnnotateVideoRequest.newBuilder() + .setInputContent( + ByteString.copyFrom( + Arrays.copyOfRange(data, i * chunkSize, i * chunkSize + chunkSize))) + .build()); + } + + // Tell the service you are done sending data + call.closeSend(); + + for (StreamingAnnotateVideoResponse response : call) { + if (response.hasError()) { + System.out.println(response.getError().getMessage()); + break; + } + + StreamingVideoAnnotationResults annotationResults = response.getAnnotationResults(); + + for (LabelAnnotation annotation : annotationResults.getLabelAnnotationsList()) { + String entity = annotation.getEntity().getDescription(); + + // There is only one frame per annotation + LabelFrame labelFrame = annotation.getFrames(0); + double offset = + labelFrame.getTimeOffset().getSeconds() + labelFrame.getTimeOffset().getNanos() / 1e9; + float confidence = labelFrame.getConfidence(); + + System.out.format("At %fs segment: %s (%f)\n", offset, entity, confidence); + } + } + System.out.println("Video streamed successfully."); + } + } +} +// [END video_streaming_automl_classification_beta] diff --git a/video/src/main/java/beta/video/StreamingAutoMlObjectTracking.java b/video/src/main/java/beta/video/StreamingAutoMlObjectTracking.java new file mode 100644 index 00000000000..7258c341b76 --- /dev/null +++ b/video/src/main/java/beta/video/StreamingAutoMlObjectTracking.java @@ -0,0 +1,123 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.video; + +// [START video_streaming_automl_object_tracking_beta] + +import com.google.api.gax.rpc.BidiStream; +import com.google.cloud.videointelligence.v1p3beta1.ObjectTrackingAnnotation; +import com.google.cloud.videointelligence.v1p3beta1.ObjectTrackingFrame; +import com.google.cloud.videointelligence.v1p3beta1.StreamingAnnotateVideoRequest; +import com.google.cloud.videointelligence.v1p3beta1.StreamingAnnotateVideoResponse; +import com.google.cloud.videointelligence.v1p3beta1.StreamingAutomlObjectTrackingConfig; +import com.google.cloud.videointelligence.v1p3beta1.StreamingFeature; +import com.google.cloud.videointelligence.v1p3beta1.StreamingVideoAnnotationResults; +import com.google.cloud.videointelligence.v1p3beta1.StreamingVideoConfig; +import com.google.cloud.videointelligence.v1p3beta1.StreamingVideoIntelligenceServiceClient; +import com.google.protobuf.ByteString; +import io.grpc.StatusRuntimeException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; + +class StreamingAutoMlObjectTracking { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String filePath = "YOUR_VIDEO_FILE"; + String projectId = "YOUR_PROJECT_ID"; + String modelId = "YOUR_AUTOML_OBJECT_TRACKING_MODEL_ID"; + streamingAutoMlObjectTracking(filePath, projectId, modelId); + } + + // Perform streaming video object tracking with an AutoML Model + static void streamingAutoMlObjectTracking(String filePath, String projectId, String modelId) + throws StatusRuntimeException, IOException { + + try (StreamingVideoIntelligenceServiceClient client = + StreamingVideoIntelligenceServiceClient.create()) { + + Path path = Paths.get(filePath); + byte[] data = Files.readAllBytes(path); + // Set the chunk size to 5MB (recommended less than 10MB). + int chunkSize = 5 * 1024 * 1024; + int numChunks = (int) Math.ceil((double) data.length / chunkSize); + + String modelPath = + String.format("projects/%s/locations/us-central1/models/%s", projectId, modelId); + + StreamingAutomlObjectTrackingConfig streamingAutomlObjectTrackingConfig = + StreamingAutomlObjectTrackingConfig.newBuilder().setModelName(modelPath).build(); + + StreamingVideoConfig streamingVideoConfig = + StreamingVideoConfig.newBuilder() + .setFeature(StreamingFeature.STREAMING_AUTOML_OBJECT_TRACKING) + .setAutomlObjectTrackingConfig(streamingAutomlObjectTrackingConfig) + .build(); + + BidiStream call = + client.streamingAnnotateVideoCallable().call(); + + // The first request must **only** contain the audio configuration: + call.send( + StreamingAnnotateVideoRequest.newBuilder().setVideoConfig(streamingVideoConfig).build()); + + // Subsequent requests must **only** contain the audio data. + // Send the requests in chunks + for (int i = 0; i < numChunks; i++) { + call.send( + StreamingAnnotateVideoRequest.newBuilder() + .setInputContent( + ByteString.copyFrom( + Arrays.copyOfRange(data, i * chunkSize, i * chunkSize + chunkSize))) + .build()); + } + + // Tell the service you are done sending data + call.closeSend(); + + for (StreamingAnnotateVideoResponse response : call) { + StreamingVideoAnnotationResults annotationResults = response.getAnnotationResults(); + + for (ObjectTrackingAnnotation objectAnnotations : + annotationResults.getObjectAnnotationsList()) { + + String entity = objectAnnotations.getEntity().getDescription(); + float confidence = objectAnnotations.getConfidence(); + long trackId = objectAnnotations.getTrackId(); + System.out.format("%s: %f (ID: %d)\n", entity, confidence, trackId); + + // In streaming, there is always one frame. + ObjectTrackingFrame frame = objectAnnotations.getFrames(0); + double offset = + frame.getTimeOffset().getSeconds() + frame.getTimeOffset().getNanos() / 1e9; + System.out.format("Offset: %f\n", offset); + + System.out.println("Bounding Box:"); + System.out.format("\tLeft: %f\n", frame.getNormalizedBoundingBox().getLeft()); + System.out.format("\tTop: %f\n", frame.getNormalizedBoundingBox().getTop()); + System.out.format("\tRight: %f\n", frame.getNormalizedBoundingBox().getRight()); + System.out.format("\tBottom: %f\n", frame.getNormalizedBoundingBox().getBottom()); + } + } + System.out.println("Video streamed successfully."); + } + } +} +// [END video_streaming_automl_object_tracking_beta] diff --git a/video/src/main/java/beta/video/StreamingExplicitContentDetection.java b/video/src/main/java/beta/video/StreamingExplicitContentDetection.java new file mode 100644 index 00000000000..2ce1c2d30f7 --- /dev/null +++ b/video/src/main/java/beta/video/StreamingExplicitContentDetection.java @@ -0,0 +1,101 @@ +/* + * 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.video; + +// [START video_streaming_explicit_content_detection_beta] + +import com.google.api.gax.rpc.BidiStream; +import com.google.cloud.videointelligence.v1p3beta1.ExplicitContentFrame; +import com.google.cloud.videointelligence.v1p3beta1.StreamingAnnotateVideoRequest; +import com.google.cloud.videointelligence.v1p3beta1.StreamingAnnotateVideoResponse; +import com.google.cloud.videointelligence.v1p3beta1.StreamingFeature; +import com.google.cloud.videointelligence.v1p3beta1.StreamingLabelDetectionConfig; +import com.google.cloud.videointelligence.v1p3beta1.StreamingVideoAnnotationResults; +import com.google.cloud.videointelligence.v1p3beta1.StreamingVideoConfig; +import com.google.cloud.videointelligence.v1p3beta1.StreamingVideoIntelligenceServiceClient; +import com.google.protobuf.ByteString; +import io.grpc.StatusRuntimeException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.concurrent.TimeoutException; + +class StreamingExplicitContentDetection { + + // Perform streaming video detection for explicit content + static void streamingExplicitContentDetection(String filePath) + throws IOException, TimeoutException, StatusRuntimeException { + // String filePath = "path_to_your_video_file"; + + try (StreamingVideoIntelligenceServiceClient client = + StreamingVideoIntelligenceServiceClient.create()) { + + Path path = Paths.get(filePath); + byte[] data = Files.readAllBytes(path); + // Set the chunk size to 5MB (recommended less than 10MB). + int chunkSize = 5 * 1024 * 1024; + int numChunks = (int) Math.ceil((double) data.length / chunkSize); + + StreamingLabelDetectionConfig labelConfig = + StreamingLabelDetectionConfig.newBuilder().setStationaryCamera(false).build(); + + StreamingVideoConfig streamingVideoConfig = + StreamingVideoConfig.newBuilder() + .setFeature(StreamingFeature.STREAMING_EXPLICIT_CONTENT_DETECTION) + .setLabelDetectionConfig(labelConfig) + .build(); + + BidiStream call = + client.streamingAnnotateVideoCallable().call(); + + // The first request must **only** contain the audio configuration: + call.send( + StreamingAnnotateVideoRequest.newBuilder().setVideoConfig(streamingVideoConfig).build()); + + // Subsequent requests must **only** contain the audio data. + // Send the requests in chunks + for (int i = 0; i < numChunks; i++) { + call.send( + StreamingAnnotateVideoRequest.newBuilder() + .setInputContent( + ByteString.copyFrom( + Arrays.copyOfRange(data, i * chunkSize, i * chunkSize + chunkSize))) + .build()); + } + + // Tell the service you are done sending data + call.closeSend(); + + for (StreamingAnnotateVideoResponse response : call) { + StreamingVideoAnnotationResults annotationResults = response.getAnnotationResults(); + + for (ExplicitContentFrame frame : + annotationResults.getExplicitAnnotation().getFramesList()) { + + double offset = + frame.getTimeOffset().getSeconds() + frame.getTimeOffset().getNanos() / 1e9; + + System.out.format("Offset: %f\n", offset); + System.out.format("\tPornography: %s", frame.getPornographyLikelihood()); + } + } + } + } +} +// [END video_streaming_explicit_content_detection_beta] diff --git a/video/src/main/java/beta/video/StreamingLabelDetection.java b/video/src/main/java/beta/video/StreamingLabelDetection.java new file mode 100644 index 00000000000..4b26534ecd2 --- /dev/null +++ b/video/src/main/java/beta/video/StreamingLabelDetection.java @@ -0,0 +1,104 @@ +/* + * 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.video; + +// [START video_streaming_label_detection_beta] + +import com.google.api.gax.rpc.BidiStream; +import com.google.cloud.videointelligence.v1p3beta1.LabelAnnotation; +import com.google.cloud.videointelligence.v1p3beta1.LabelFrame; +import com.google.cloud.videointelligence.v1p3beta1.StreamingAnnotateVideoRequest; +import com.google.cloud.videointelligence.v1p3beta1.StreamingAnnotateVideoResponse; +import com.google.cloud.videointelligence.v1p3beta1.StreamingFeature; +import com.google.cloud.videointelligence.v1p3beta1.StreamingLabelDetectionConfig; +import com.google.cloud.videointelligence.v1p3beta1.StreamingVideoAnnotationResults; +import com.google.cloud.videointelligence.v1p3beta1.StreamingVideoConfig; +import com.google.cloud.videointelligence.v1p3beta1.StreamingVideoIntelligenceServiceClient; +import com.google.protobuf.ByteString; +import io.grpc.StatusRuntimeException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.concurrent.TimeoutException; + +class StreamingLabelDetection { + + // Perform streaming video label detection + static void streamingLabelDetection(String filePath) + throws IOException, TimeoutException, StatusRuntimeException { + // String filePath = "path_to_your_video_file"; + + try (StreamingVideoIntelligenceServiceClient client = + StreamingVideoIntelligenceServiceClient.create()) { + + Path path = Paths.get(filePath); + byte[] data = Files.readAllBytes(path); + // Set the chunk size to 5MB (recommended less than 10MB). + int chunkSize = 5 * 1024 * 1024; + int numChunks = (int) Math.ceil((double) data.length / chunkSize); + + StreamingLabelDetectionConfig labelConfig = + StreamingLabelDetectionConfig.newBuilder().setStationaryCamera(false).build(); + + StreamingVideoConfig streamingVideoConfig = + StreamingVideoConfig.newBuilder() + .setFeature(StreamingFeature.STREAMING_LABEL_DETECTION) + .setLabelDetectionConfig(labelConfig) + .build(); + + BidiStream call = + client.streamingAnnotateVideoCallable().call(); + + // The first request must **only** contain the audio configuration: + call.send( + StreamingAnnotateVideoRequest.newBuilder().setVideoConfig(streamingVideoConfig).build()); + + // Subsequent requests must **only** contain the audio data. + // Send the requests in chunks + for (int i = 0; i < numChunks; i++) { + call.send( + StreamingAnnotateVideoRequest.newBuilder() + .setInputContent( + ByteString.copyFrom( + Arrays.copyOfRange(data, i * chunkSize, i * chunkSize + chunkSize))) + .build()); + } + + // Tell the service you are done sending data + call.closeSend(); + + for (StreamingAnnotateVideoResponse response : call) { + StreamingVideoAnnotationResults annotationResults = response.getAnnotationResults(); + + for (LabelAnnotation annotation : annotationResults.getLabelAnnotationsList()) { + String entity = annotation.getEntity().getDescription(); + + // There is only one frame per annotation + LabelFrame labelFrame = annotation.getFrames(0); + double offset = + labelFrame.getTimeOffset().getSeconds() + labelFrame.getTimeOffset().getNanos() / 1e9; + float confidence = labelFrame.getConfidence(); + + System.out.format("%fs: %s (%f)\n", offset, entity, confidence); + } + } + } + } +} +// [END video_streaming_label_detection_beta] diff --git a/video/src/main/java/beta/video/StreamingObjectTracking.java b/video/src/main/java/beta/video/StreamingObjectTracking.java new file mode 100644 index 00000000000..0fe458f28a8 --- /dev/null +++ b/video/src/main/java/beta/video/StreamingObjectTracking.java @@ -0,0 +1,113 @@ +/* + * 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.video; + +// [START video_streaming_object_tracking_beta] + +import com.google.api.gax.rpc.BidiStream; +import com.google.cloud.videointelligence.v1p3beta1.ObjectTrackingAnnotation; +import com.google.cloud.videointelligence.v1p3beta1.ObjectTrackingFrame; +import com.google.cloud.videointelligence.v1p3beta1.StreamingAnnotateVideoRequest; +import com.google.cloud.videointelligence.v1p3beta1.StreamingAnnotateVideoResponse; +import com.google.cloud.videointelligence.v1p3beta1.StreamingFeature; +import com.google.cloud.videointelligence.v1p3beta1.StreamingLabelDetectionConfig; +import com.google.cloud.videointelligence.v1p3beta1.StreamingVideoAnnotationResults; +import com.google.cloud.videointelligence.v1p3beta1.StreamingVideoConfig; +import com.google.cloud.videointelligence.v1p3beta1.StreamingVideoIntelligenceServiceClient; +import com.google.protobuf.ByteString; +import io.grpc.StatusRuntimeException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.concurrent.TimeoutException; + +class StreamingObjectTracking { + + // Perform streaming video object tracking + static void streamingObjectTracking(String filePath) + throws IOException, TimeoutException, StatusRuntimeException { + // String filePath = "path_to_your_video_file"; + + try (StreamingVideoIntelligenceServiceClient client = + StreamingVideoIntelligenceServiceClient.create()) { + + Path path = Paths.get(filePath); + byte[] data = Files.readAllBytes(path); + // Set the chunk size to 5MB (recommended less than 10MB). + int chunkSize = 5 * 1024 * 1024; + int numChunks = (int) Math.ceil((double) data.length / chunkSize); + + StreamingLabelDetectionConfig labelConfig = + StreamingLabelDetectionConfig.newBuilder().setStationaryCamera(false).build(); + + StreamingVideoConfig streamingVideoConfig = + StreamingVideoConfig.newBuilder() + .setFeature(StreamingFeature.STREAMING_OBJECT_TRACKING) + .setLabelDetectionConfig(labelConfig) + .build(); + + BidiStream call = + client.streamingAnnotateVideoCallable().call(); + + // The first request must **only** contain the audio configuration: + call.send( + StreamingAnnotateVideoRequest.newBuilder().setVideoConfig(streamingVideoConfig).build()); + + // Subsequent requests must **only** contain the audio data. + // Send the requests in chunks + for (int i = 0; i < numChunks; i++) { + call.send( + StreamingAnnotateVideoRequest.newBuilder() + .setInputContent( + ByteString.copyFrom( + Arrays.copyOfRange(data, i * chunkSize, i * chunkSize + chunkSize))) + .build()); + } + + // Tell the service you are done sending data + call.closeSend(); + + for (StreamingAnnotateVideoResponse response : call) { + StreamingVideoAnnotationResults annotationResults = response.getAnnotationResults(); + + for (ObjectTrackingAnnotation objectAnnotations : + annotationResults.getObjectAnnotationsList()) { + + String entity = objectAnnotations.getEntity().getDescription(); + float confidence = objectAnnotations.getConfidence(); + long trackId = objectAnnotations.getTrackId(); + System.out.format("%s: %f (ID: %d)\n", entity, confidence, trackId); + + // In streaming, there is always one frame. + ObjectTrackingFrame frame = objectAnnotations.getFrames(0); + double offset = + frame.getTimeOffset().getSeconds() + frame.getTimeOffset().getNanos() / 1e9; + System.out.format("Offset: %f\n", offset); + + System.out.println("Bounding Box:"); + System.out.format("\tLeft: %f\n", frame.getNormalizedBoundingBox().getLeft()); + System.out.format("\tTop: %f\n", frame.getNormalizedBoundingBox().getTop()); + System.out.format("\tRight: %f\n", frame.getNormalizedBoundingBox().getRight()); + System.out.format("\tBottom: %f\n", frame.getNormalizedBoundingBox().getBottom()); + } + } + } + } +} +// [END video_streaming_object_tracking_beta] diff --git a/video/src/main/java/beta/video/StreamingShotChangeDetection.java b/video/src/main/java/beta/video/StreamingShotChangeDetection.java new file mode 100644 index 00000000000..8ce99489bf7 --- /dev/null +++ b/video/src/main/java/beta/video/StreamingShotChangeDetection.java @@ -0,0 +1,105 @@ +/* + * 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.video; + +// [START video_streaming_shot_change_detection_beta] + +import com.google.api.gax.rpc.BidiStream; +import com.google.cloud.videointelligence.v1p3beta1.StreamingAnnotateVideoRequest; +import com.google.cloud.videointelligence.v1p3beta1.StreamingAnnotateVideoResponse; +import com.google.cloud.videointelligence.v1p3beta1.StreamingFeature; +import com.google.cloud.videointelligence.v1p3beta1.StreamingLabelDetectionConfig; +import com.google.cloud.videointelligence.v1p3beta1.StreamingVideoAnnotationResults; +import com.google.cloud.videointelligence.v1p3beta1.StreamingVideoConfig; +import com.google.cloud.videointelligence.v1p3beta1.StreamingVideoIntelligenceServiceClient; +import com.google.cloud.videointelligence.v1p3beta1.VideoSegment; +import com.google.protobuf.ByteString; +import io.grpc.StatusRuntimeException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.concurrent.TimeoutException; + +class StreamingShotChangeDetection { + + // Perform streaming video detection for shot changes + static void streamingShotChangeDetection(String filePath) + throws IOException, TimeoutException, StatusRuntimeException { + // String filePath = "path_to_your_video_file"; + + try (StreamingVideoIntelligenceServiceClient client = + StreamingVideoIntelligenceServiceClient.create()) { + + Path path = Paths.get(filePath); + byte[] data = Files.readAllBytes(path); + // Set the chunk size to 5MB (recommended less than 10MB). + int chunkSize = 5 * 1024 * 1024; + int numChunks = (int) Math.ceil((double) data.length / chunkSize); + + StreamingLabelDetectionConfig labelConfig = + StreamingLabelDetectionConfig.newBuilder().setStationaryCamera(false).build(); + + StreamingVideoConfig streamingVideoConfig = + StreamingVideoConfig.newBuilder() + .setFeature(StreamingFeature.STREAMING_SHOT_CHANGE_DETECTION) + .setLabelDetectionConfig(labelConfig) + .build(); + + BidiStream call = + client.streamingAnnotateVideoCallable().call(); + + // The first request must **only** contain the audio configuration: + call.send( + StreamingAnnotateVideoRequest.newBuilder().setVideoConfig(streamingVideoConfig).build()); + + // Subsequent requests must **only** contain the audio data. + // Send the requests in chunks + for (int i = 0; i < numChunks; i++) { + call.send( + StreamingAnnotateVideoRequest.newBuilder() + .setInputContent( + ByteString.copyFrom( + Arrays.copyOfRange(data, i * chunkSize, i * chunkSize + chunkSize))) + .build()); + } + + // Tell the service you are done sending data + call.closeSend(); + + for (StreamingAnnotateVideoResponse response : call) { + StreamingVideoAnnotationResults annotationResults = response.getAnnotationResults(); + if (response.hasError()) { + System.out.println(response.getError().getMessage()); + System.out.format( + "Error was occured with the following status: %s\n", response.getError()); + } + for (VideoSegment segment : annotationResults.getShotAnnotationsList()) { + double startTimeOffset = + segment.getStartTimeOffset().getSeconds() + + segment.getStartTimeOffset().getNanos() / 1e9; + double endTimeOffset = + segment.getEndTimeOffset().getSeconds() + segment.getEndTimeOffset().getNanos() / 1e9; + + System.out.format("Shot: %fs to %fs\n", startTimeOffset, endTimeOffset); + } + } + } + } +} +// [END video_streaming_shot_change_detection_beta] diff --git a/video/src/main/java/beta/video/TextDetection.java b/video/src/main/java/beta/video/TextDetection.java new file mode 100644 index 00000000000..2574a776589 --- /dev/null +++ b/video/src/main/java/beta/video/TextDetection.java @@ -0,0 +1,182 @@ +/* + * 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 beta.video; + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.videointelligence.v1p2beta1.AnnotateVideoProgress; +import com.google.cloud.videointelligence.v1p2beta1.AnnotateVideoRequest; +import com.google.cloud.videointelligence.v1p2beta1.AnnotateVideoResponse; +import com.google.cloud.videointelligence.v1p2beta1.Feature; +import com.google.cloud.videointelligence.v1p2beta1.NormalizedVertex; +import com.google.cloud.videointelligence.v1p2beta1.TextAnnotation; +import com.google.cloud.videointelligence.v1p2beta1.TextFrame; +import com.google.cloud.videointelligence.v1p2beta1.TextSegment; +import com.google.cloud.videointelligence.v1p2beta1.VideoAnnotationResults; +import com.google.cloud.videointelligence.v1p2beta1.VideoIntelligenceServiceClient; +import com.google.cloud.videointelligence.v1p2beta1.VideoSegment; +import com.google.protobuf.ByteString; +import com.google.protobuf.Duration; +import io.grpc.StatusRuntimeException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class TextDetection { + + // [START video_detect_text_beta] + + /** + * Detect text in a video. + * + * @param filePath the path to the video file to analyze. + */ + public static VideoAnnotationResults detectText(String filePath) + throws IOException, StatusRuntimeException, TimeoutException, ExecutionException, + InterruptedException { + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Read file + Path path = Paths.get(filePath); + byte[] data = Files.readAllBytes(path); + + // Create the request + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputContent(ByteString.copyFrom(data)) + .addFeatures(Feature.TEXT_DETECTION) + .build(); + + // asynchronously perform object tracking on videos + OperationFuture future = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + // The first result is retrieved because a single video was processed. + AnnotateVideoResponse response = future.get(600, TimeUnit.SECONDS); + VideoAnnotationResults results = response.getAnnotationResults(0); + + // Get only the first annotation for demo purposes. + TextAnnotation annotation = results.getTextAnnotations(0); + System.out.println("Text: " + annotation.getText()); + + // Get the first text segment. + TextSegment textSegment = annotation.getSegments(0); + System.out.println("Confidence: " + textSegment.getConfidence()); + // For the text segment display it's time offset + VideoSegment videoSegment = textSegment.getSegment(); + Duration startTimeOffset = videoSegment.getStartTimeOffset(); + Duration endTimeOffset = videoSegment.getEndTimeOffset(); + // Display the offset times in seconds, 1e9 is part of the formula to convert nanos to seconds + System.out.println( + String.format( + "Start time: %.2f", startTimeOffset.getSeconds() + startTimeOffset.getNanos() / 1e9)); + System.out.println( + String.format( + "End time: %.2f", endTimeOffset.getSeconds() + endTimeOffset.getNanos() / 1e9)); + + // Show the first result for the first frame in the segment. + TextFrame textFrame = textSegment.getFrames(0); + Duration timeOffset = textFrame.getTimeOffset(); + System.out.println( + String.format( + "Time offset for the first frame: %.2f", + timeOffset.getSeconds() + timeOffset.getNanos() / 1e9)); + + // Display the rotated bounding box for where the text is on the frame. + System.out.println("Rotated Bounding Box Vertices:"); + List vertices = textFrame.getRotatedBoundingBox().getVerticesList(); + for (NormalizedVertex normalizedVertex : vertices) { + System.out.println( + String.format( + "\tVertex.x: %.2f, Vertex.y: %.2f", + normalizedVertex.getX(), normalizedVertex.getY())); + } + return results; + } + } + // [END video_detect_text_beta] + + // [START video_detect_text_gcs_beta] + + /** + * Detect Text in a video. + * + * @param gcsUri the path to the video file to analyze. + */ + public static VideoAnnotationResults detectTextGcs(String gcsUri) throws Exception { + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Create the request + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputUri(gcsUri) + .addFeatures(Feature.TEXT_DETECTION) + .build(); + + // asynchronously perform object tracking on videos + OperationFuture future = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + // The first result is retrieved because a single video was processed. + AnnotateVideoResponse response = future.get(600, TimeUnit.SECONDS); + VideoAnnotationResults results = response.getAnnotationResults(0); + + // Get only the first annotation for demo purposes. + TextAnnotation annotation = results.getTextAnnotations(0); + System.out.println("Text: " + annotation.getText()); + + // Get the first text segment. + TextSegment textSegment = annotation.getSegments(0); + System.out.println("Confidence: " + textSegment.getConfidence()); + // For the text segment display it's time offset + VideoSegment videoSegment = textSegment.getSegment(); + Duration startTimeOffset = videoSegment.getStartTimeOffset(); + Duration endTimeOffset = videoSegment.getEndTimeOffset(); + // Display the offset times in seconds, 1e9 is part of the formula to convert nanos to seconds + System.out.println( + String.format( + "Start time: %.2f", startTimeOffset.getSeconds() + startTimeOffset.getNanos() / 1e9)); + System.out.println( + String.format( + "End time: %.2f", endTimeOffset.getSeconds() + endTimeOffset.getNanos() / 1e9)); + + // Show the first result for the first frame in the segment. + TextFrame textFrame = textSegment.getFrames(0); + Duration timeOffset = textFrame.getTimeOffset(); + System.out.println( + String.format( + "Time offset for the first frame: %.2f", + timeOffset.getSeconds() + timeOffset.getNanos() / 1e9)); + + // Display the rotated bounding box for where the text is on the frame. + System.out.println("Rotated Bounding Box Vertices:"); + List vertices = textFrame.getRotatedBoundingBox().getVerticesList(); + for (NormalizedVertex normalizedVertex : vertices) { + System.out.println( + String.format( + "\tVertex.x: %.2f, Vertex.y: %.2f", + normalizedVertex.getX(), normalizedVertex.getY())); + } + return results; + } + } + // [END video_detect_text_gcs_beta] +} diff --git a/video/src/main/java/beta/video/TrackObjects.java b/video/src/main/java/beta/video/TrackObjects.java new file mode 100644 index 00000000000..30ba1ca0013 --- /dev/null +++ b/video/src/main/java/beta/video/TrackObjects.java @@ -0,0 +1,179 @@ +/* + * 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 beta.video; + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.videointelligence.v1p2beta1.AnnotateVideoProgress; +import com.google.cloud.videointelligence.v1p2beta1.AnnotateVideoRequest; +import com.google.cloud.videointelligence.v1p2beta1.AnnotateVideoResponse; +import com.google.cloud.videointelligence.v1p2beta1.Entity; +import com.google.cloud.videointelligence.v1p2beta1.Feature; +import com.google.cloud.videointelligence.v1p2beta1.NormalizedBoundingBox; +import com.google.cloud.videointelligence.v1p2beta1.ObjectTrackingAnnotation; +import com.google.cloud.videointelligence.v1p2beta1.ObjectTrackingFrame; +import com.google.cloud.videointelligence.v1p2beta1.VideoAnnotationResults; +import com.google.cloud.videointelligence.v1p2beta1.VideoIntelligenceServiceClient; +import com.google.cloud.videointelligence.v1p2beta1.VideoSegment; +import com.google.protobuf.ByteString; +import com.google.protobuf.Duration; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.TimeUnit; + +public class TrackObjects { + + // [START video_object_tracking_beta] + /** + * Track objects in a video. + * + * @param filePath the path to the video file to analyze. + */ + public static VideoAnnotationResults trackObjects(String filePath) throws Exception { + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Read file + Path path = Paths.get(filePath); + byte[] data = Files.readAllBytes(path); + + // Create the request + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputContent(ByteString.copyFrom(data)) + .addFeatures(Feature.OBJECT_TRACKING) + .setLocationId("us-east1") + .build(); + + // asynchronously perform object tracking on videos + OperationFuture future = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + // The first result is retrieved because a single video was processed. + AnnotateVideoResponse response = future.get(600, TimeUnit.SECONDS); + VideoAnnotationResults results = response.getAnnotationResults(0); + + // Get only the first annotation for demo purposes. + ObjectTrackingAnnotation annotation = results.getObjectAnnotations(0); + System.out.println("Confidence: " + annotation.getConfidence()); + + if (annotation.hasEntity()) { + Entity entity = annotation.getEntity(); + System.out.println("Entity description: " + entity.getDescription()); + System.out.println("Entity id:: " + entity.getEntityId()); + } + + if (annotation.hasSegment()) { + VideoSegment videoSegment = annotation.getSegment(); + Duration startTimeOffset = videoSegment.getStartTimeOffset(); + Duration endTimeOffset = videoSegment.getEndTimeOffset(); + // Display the segment time in seconds, 1e9 converts nanos to seconds + System.out.println( + String.format( + "Segment: %.2fs to %.2fs", + startTimeOffset.getSeconds() + startTimeOffset.getNanos() / 1e9, + endTimeOffset.getSeconds() + endTimeOffset.getNanos() / 1e9)); + } + + // Here we print only the bounding box of the first frame in this segment. + ObjectTrackingFrame frame = annotation.getFrames(0); + // Display the offset time in seconds, 1e9 converts nanos to seconds + Duration timeOffset = frame.getTimeOffset(); + System.out.println( + String.format( + "Time offset of the first frame: %.2fs", + timeOffset.getSeconds() + timeOffset.getNanos() / 1e9)); + + // Display the bounding box of the detected object + NormalizedBoundingBox normalizedBoundingBox = frame.getNormalizedBoundingBox(); + System.out.println("Bounding box position:"); + System.out.println("\tleft: " + normalizedBoundingBox.getLeft()); + System.out.println("\ttop: " + normalizedBoundingBox.getTop()); + System.out.println("\tright: " + normalizedBoundingBox.getRight()); + System.out.println("\tbottom: " + normalizedBoundingBox.getBottom()); + return results; + } + } + // [END video_object_tracking_beta] + + // [START video_object_tracking_gcs_beta] + /** + * Track objects in a video. + * + * @param gcsUri the path to the video file to analyze. + */ + public static VideoAnnotationResults trackObjectsGcs(String gcsUri) throws Exception { + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Create the request + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputUri(gcsUri) + .addFeatures(Feature.OBJECT_TRACKING) + .setLocationId("us-east1") + .build(); + + // asynchronously perform object tracking on videos + OperationFuture future = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + // The first result is retrieved because a single video was processed. + AnnotateVideoResponse response = future.get(450, TimeUnit.SECONDS); + VideoAnnotationResults results = response.getAnnotationResults(0); + + // Get only the first annotation for demo purposes. + ObjectTrackingAnnotation annotation = results.getObjectAnnotations(0); + System.out.println("Confidence: " + annotation.getConfidence()); + + if (annotation.hasEntity()) { + Entity entity = annotation.getEntity(); + System.out.println("Entity description: " + entity.getDescription()); + System.out.println("Entity id:: " + entity.getEntityId()); + } + + if (annotation.hasSegment()) { + VideoSegment videoSegment = annotation.getSegment(); + Duration startTimeOffset = videoSegment.getStartTimeOffset(); + Duration endTimeOffset = videoSegment.getEndTimeOffset(); + // Display the segment time in seconds, 1e9 converts nanos to seconds + System.out.println( + String.format( + "Segment: %.2fs to %.2fs", + startTimeOffset.getSeconds() + startTimeOffset.getNanos() / 1e9, + endTimeOffset.getSeconds() + endTimeOffset.getNanos() / 1e9)); + } + + // Here we print only the bounding box of the first frame in this segment. + ObjectTrackingFrame frame = annotation.getFrames(0); + // Display the offset time in seconds, 1e9 converts nanos to seconds + Duration timeOffset = frame.getTimeOffset(); + System.out.println( + String.format( + "Time offset of the first frame: %.2fs", + timeOffset.getSeconds() + timeOffset.getNanos() / 1e9)); + + // Display the bounding box of the detected object + NormalizedBoundingBox normalizedBoundingBox = frame.getNormalizedBoundingBox(); + System.out.println("Bounding box position:"); + System.out.println("\tleft: " + normalizedBoundingBox.getLeft()); + System.out.println("\ttop: " + normalizedBoundingBox.getTop()); + System.out.println("\tright: " + normalizedBoundingBox.getRight()); + System.out.println("\tbottom: " + normalizedBoundingBox.getBottom()); + return results; + } + } + // [END video_object_tracking_gcs_beta] +} diff --git a/video/src/main/java/com/example/video/Detect.java b/video/src/main/java/com/example/video/Detect.java new file mode 100644 index 00000000000..ec643387222 --- /dev/null +++ b/video/src/main/java/com/example/video/Detect.java @@ -0,0 +1,410 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.video; + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.videointelligence.v1.AnnotateVideoProgress; +import com.google.cloud.videointelligence.v1.AnnotateVideoRequest; +import com.google.cloud.videointelligence.v1.AnnotateVideoResponse; +import com.google.cloud.videointelligence.v1.Entity; +import com.google.cloud.videointelligence.v1.ExplicitContentFrame; +import com.google.cloud.videointelligence.v1.Feature; +import com.google.cloud.videointelligence.v1.LabelAnnotation; +import com.google.cloud.videointelligence.v1.LabelSegment; +import com.google.cloud.videointelligence.v1.SpeechRecognitionAlternative; +import com.google.cloud.videointelligence.v1.SpeechTranscription; +import com.google.cloud.videointelligence.v1.SpeechTranscriptionConfig; +import com.google.cloud.videointelligence.v1.VideoAnnotationResults; +import com.google.cloud.videointelligence.v1.VideoContext; +import com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient; +import com.google.cloud.videointelligence.v1.VideoSegment; +import com.google.cloud.videointelligence.v1.WordInfo; +import com.google.protobuf.ByteString; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.TimeUnit; + +public class Detect { + /** + * Detects labels, shots, and explicit content in a video using the Video Intelligence API + * + * @param args specifies features to detect and the path to the video on Google Cloud Storage. + */ + public static void main(String[] args) { + try { + argsHelper(args); + } catch (Exception e) { + System.out.println("Exception while running:\n" + e.getMessage() + "\n"); + e.printStackTrace(System.out); + } + } + + /** + * Helper that handles the input passed to the program. + * + * @param args specifies features to detect and the path to the video on Google Cloud Storage. + * @throws IOException on Input/Output errors. + */ + public static void argsHelper(String[] args) throws Exception { + if (args.length < 1) { + System.out.println("Usage:"); + System.out.printf( + "\tjava %s \"\" \"\"\n" + + "Commands:\n" + + "\tlabels | shots\n" + + "Path:\n\tA URI for a Cloud Storage resource (gs://...)\n" + + "Examples: ", + Detect.class.getCanonicalName()); + return; + } + String command = args[0]; + String path = args.length > 1 ? args[1] : ""; + + if (command.equals("labels")) { + analyzeLabels(path); + } + if (command.equals("labels-file")) { + analyzeLabelsFile(path); + } + if (command.equals("shots")) { + analyzeShots(path); + } + if (command.equals("explicit-content")) { + analyzeExplicitContent(path); + } + if (command.equals("speech-transcription")) { + speechTranscription(path); + } + } + + /** + * Performs label analysis on the video at the provided Cloud Storage path. + * + * @param gcsUri the path to the video file to analyze. + */ + public static void analyzeLabels(String gcsUri) throws Exception { + // [START video_analyze_labels_gcs] + // Instantiate a com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Provide path to file hosted on GCS as "gs://bucket-name/..." + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputUri(gcsUri) + .addFeatures(Feature.LABEL_DETECTION) + .build(); + // Create an operation that will contain the response when the operation completes. + OperationFuture response = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + for (VideoAnnotationResults results : response.get().getAnnotationResultsList()) { + // process video / segment level label annotations + System.out.println("Locations: "); + for (LabelAnnotation labelAnnotation : results.getSegmentLabelAnnotationsList()) { + System.out.println("Video label: " + labelAnnotation.getEntity().getDescription()); + // categories + for (Entity categoryEntity : labelAnnotation.getCategoryEntitiesList()) { + System.out.println("Video label category: " + categoryEntity.getDescription()); + } + // segments + for (LabelSegment segment : labelAnnotation.getSegmentsList()) { + double startTime = + segment.getSegment().getStartTimeOffset().getSeconds() + + segment.getSegment().getStartTimeOffset().getNanos() / 1e9; + double endTime = + segment.getSegment().getEndTimeOffset().getSeconds() + + segment.getSegment().getEndTimeOffset().getNanos() / 1e9; + System.out.printf("Segment location: %.3f:%.3f\n", startTime, endTime); + System.out.println("Confidence: " + segment.getConfidence()); + } + } + + // process shot label annotations + for (LabelAnnotation labelAnnotation : results.getShotLabelAnnotationsList()) { + System.out.println("Shot label: " + labelAnnotation.getEntity().getDescription()); + // categories + for (Entity categoryEntity : labelAnnotation.getCategoryEntitiesList()) { + System.out.println("Shot label category: " + categoryEntity.getDescription()); + } + // segments + for (LabelSegment segment : labelAnnotation.getSegmentsList()) { + double startTime = + segment.getSegment().getStartTimeOffset().getSeconds() + + segment.getSegment().getStartTimeOffset().getNanos() / 1e9; + double endTime = + segment.getSegment().getEndTimeOffset().getSeconds() + + segment.getSegment().getEndTimeOffset().getNanos() / 1e9; + System.out.printf("Segment location: %.3f:%.3f\n", startTime, endTime); + System.out.println("Confidence: " + segment.getConfidence()); + } + } + + // process frame label annotations + for (LabelAnnotation labelAnnotation : results.getFrameLabelAnnotationsList()) { + System.out.println("Frame label: " + labelAnnotation.getEntity().getDescription()); + // categories + for (Entity categoryEntity : labelAnnotation.getCategoryEntitiesList()) { + System.out.println("Frame label category: " + categoryEntity.getDescription()); + } + // segments + for (LabelSegment segment : labelAnnotation.getSegmentsList()) { + double startTime = + segment.getSegment().getStartTimeOffset().getSeconds() + + segment.getSegment().getStartTimeOffset().getNanos() / 1e9; + double endTime = + segment.getSegment().getEndTimeOffset().getSeconds() + + segment.getSegment().getEndTimeOffset().getNanos() / 1e9; + System.out.printf("Segment location: %.3f:%.2f\n", startTime, endTime); + System.out.println("Confidence: " + segment.getConfidence()); + } + } + } + } + // [END video_analyze_labels_gcs] + } + + /** + * Performs label analysis on the video at the provided file path. + * + * @param filePath the path to the video file to analyze. + */ + public static void analyzeLabelsFile(String filePath) throws Exception { + // [START video_analyze_labels] + // Instantiate a com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Read file and encode into Base64 + Path path = Paths.get(filePath); + byte[] data = Files.readAllBytes(path); + + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputContent(ByteString.copyFrom(data)) + .addFeatures(Feature.LABEL_DETECTION) + .build(); + // Create an operation that will contain the response when the operation completes. + OperationFuture response = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + for (VideoAnnotationResults results : response.get().getAnnotationResultsList()) { + // process video / segment level label annotations + System.out.println("Locations: "); + for (LabelAnnotation labelAnnotation : results.getSegmentLabelAnnotationsList()) { + System.out.println("Video label: " + labelAnnotation.getEntity().getDescription()); + // categories + for (Entity categoryEntity : labelAnnotation.getCategoryEntitiesList()) { + System.out.println("Video label category: " + categoryEntity.getDescription()); + } + // segments + for (LabelSegment segment : labelAnnotation.getSegmentsList()) { + double startTime = + segment.getSegment().getStartTimeOffset().getSeconds() + + segment.getSegment().getStartTimeOffset().getNanos() / 1e9; + double endTime = + segment.getSegment().getEndTimeOffset().getSeconds() + + segment.getSegment().getEndTimeOffset().getNanos() / 1e9; + System.out.printf("Segment location: %.3f:%.2f\n", startTime, endTime); + System.out.println("Confidence: " + segment.getConfidence()); + } + } + + // process shot label annotations + for (LabelAnnotation labelAnnotation : results.getShotLabelAnnotationsList()) { + System.out.println("Shot label: " + labelAnnotation.getEntity().getDescription()); + // categories + for (Entity categoryEntity : labelAnnotation.getCategoryEntitiesList()) { + System.out.println("Shot label category: " + categoryEntity.getDescription()); + } + // segments + for (LabelSegment segment : labelAnnotation.getSegmentsList()) { + double startTime = + segment.getSegment().getStartTimeOffset().getSeconds() + + segment.getSegment().getStartTimeOffset().getNanos() / 1e9; + double endTime = + segment.getSegment().getEndTimeOffset().getSeconds() + + segment.getSegment().getEndTimeOffset().getNanos() / 1e9; + System.out.printf("Segment location: %.3f:%.2f\n", startTime, endTime); + System.out.println("Confidence: " + segment.getConfidence()); + } + } + + // process frame label annotations + for (LabelAnnotation labelAnnotation : results.getFrameLabelAnnotationsList()) { + System.out.println("Frame label: " + labelAnnotation.getEntity().getDescription()); + // categories + for (Entity categoryEntity : labelAnnotation.getCategoryEntitiesList()) { + System.out.println("Frame label category: " + categoryEntity.getDescription()); + } + // segments + for (LabelSegment segment : labelAnnotation.getSegmentsList()) { + double startTime = + segment.getSegment().getStartTimeOffset().getSeconds() + + segment.getSegment().getStartTimeOffset().getNanos() / 1e9; + double endTime = + segment.getSegment().getEndTimeOffset().getSeconds() + + segment.getSegment().getEndTimeOffset().getNanos() / 1e9; + System.out.printf("Segment location: %.3f:%.2f\n", startTime, endTime); + System.out.println("Confidence: " + segment.getConfidence()); + } + } + } + } + // [END video_analyze_labels] + } + + /** + * Performs shot analysis on the video at the provided Cloud Storage path. + * + * @param gcsUri the path to the video file to analyze. + */ + public static void analyzeShots(String gcsUri) throws Exception { + // [START video_analyze_shots] + // Instantiate a com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Provide path to file hosted on GCS as "gs://bucket-name/..." + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputUri(gcsUri) + .addFeatures(Feature.SHOT_CHANGE_DETECTION) + .build(); + + // Create an operation that will contain the response when the operation completes. + OperationFuture response = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + // Print detected shot changes and their location ranges in the analyzed video. + for (VideoAnnotationResults result : response.get().getAnnotationResultsList()) { + if (result.getShotAnnotationsCount() > 0) { + System.out.println("Shots: "); + for (VideoSegment segment : result.getShotAnnotationsList()) { + double startTime = + segment.getStartTimeOffset().getSeconds() + + segment.getStartTimeOffset().getNanos() / 1e9; + double endTime = + segment.getEndTimeOffset().getSeconds() + + segment.getEndTimeOffset().getNanos() / 1e9; + System.out.printf("Location: %.3f:%.3f\n", startTime, endTime); + } + } else { + System.out.println("No shot changes detected in " + gcsUri); + } + } + } + // [END video_analyze_shots] + } + + /** + * Performs explicit content analysis on the video at the provided Cloud Storage path. + * + * @param gcsUri the path to the video file to analyze. + */ + public static void analyzeExplicitContent(String gcsUri) throws Exception { + // [START video_analyze_explicit_content] + // Instantiate a com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Create an operation that will contain the response when the operation completes. + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputUri(gcsUri) + .addFeatures(Feature.EXPLICIT_CONTENT_DETECTION) + .build(); + + OperationFuture response = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + // Print detected annotations and their positions in the analyzed video. + for (VideoAnnotationResults result : response.get().getAnnotationResultsList()) { + for (ExplicitContentFrame frame : result.getExplicitAnnotation().getFramesList()) { + double frameTime = + frame.getTimeOffset().getSeconds() + frame.getTimeOffset().getNanos() / 1e9; + System.out.printf("Location: %.3fs\n", frameTime); + System.out.println("Adult: " + frame.getPornographyLikelihood()); + } + } + // [END video_analyze_explicit_content] + } + } + + /** + * Transcribe speech from a video stored on GCS. + * + * @param gcsUri the path to the video file to analyze. + */ + public static void speechTranscription(String gcsUri) throws Exception { + // [START video_speech_transcription_gcs] + // Instantiate a com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Set the language code + SpeechTranscriptionConfig config = + SpeechTranscriptionConfig.newBuilder() + .setLanguageCode("en-US") + .setEnableAutomaticPunctuation(true) + .build(); + + // Set the video context with the above configuration + VideoContext context = VideoContext.newBuilder().setSpeechTranscriptionConfig(config).build(); + + // Create the request + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputUri(gcsUri) + .addFeatures(Feature.SPEECH_TRANSCRIPTION) + .setVideoContext(context) + .build(); + + // asynchronously perform speech transcription on videos + OperationFuture response = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + // Display the results + for (VideoAnnotationResults results : + response.get(600, TimeUnit.SECONDS).getAnnotationResultsList()) { + for (SpeechTranscription speechTranscription : results.getSpeechTranscriptionsList()) { + try { + // Print the transcription + if (speechTranscription.getAlternativesCount() > 0) { + SpeechRecognitionAlternative alternative = speechTranscription.getAlternatives(0); + + System.out.printf("Transcript: %s\n", alternative.getTranscript()); + System.out.printf("Confidence: %.2f\n", alternative.getConfidence()); + + System.out.println("Word level information:"); + for (WordInfo wordInfo : alternative.getWordsList()) { + double startTime = + wordInfo.getStartTime().getSeconds() + wordInfo.getStartTime().getNanos() / 1e9; + double endTime = + wordInfo.getEndTime().getSeconds() + wordInfo.getEndTime().getNanos() / 1e9; + System.out.printf( + "\t%4.2fs - %4.2fs: %s\n", startTime, endTime, wordInfo.getWord()); + } + } else { + System.out.println("No transcription found"); + } + } catch (IndexOutOfBoundsException ioe) { + System.out.println("Could not retrieve frame: " + ioe.getMessage()); + } + } + } + } + // [END video_speech_transcription_gcs] + } +} diff --git a/video/src/main/java/com/example/video/LogoDetection.java b/video/src/main/java/com/example/video/LogoDetection.java new file mode 100644 index 00000000000..09a8fed0a87 --- /dev/null +++ b/video/src/main/java/com/example/video/LogoDetection.java @@ -0,0 +1,141 @@ +/* + * 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.video; + +// [START video_detect_logo] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.videointelligence.v1.AnnotateVideoProgress; +import com.google.cloud.videointelligence.v1.AnnotateVideoRequest; +import com.google.cloud.videointelligence.v1.AnnotateVideoResponse; +import com.google.cloud.videointelligence.v1.DetectedAttribute; +import com.google.cloud.videointelligence.v1.Entity; +import com.google.cloud.videointelligence.v1.Feature; +import com.google.cloud.videointelligence.v1.LogoRecognitionAnnotation; +import com.google.cloud.videointelligence.v1.NormalizedBoundingBox; +import com.google.cloud.videointelligence.v1.TimestampedObject; +import com.google.cloud.videointelligence.v1.Track; +import com.google.cloud.videointelligence.v1.VideoAnnotationResults; +import com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient; +import com.google.cloud.videointelligence.v1.VideoSegment; +import com.google.protobuf.ByteString; +import com.google.protobuf.Duration; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class LogoDetection { + + public static void detectLogo() throws Exception { + // TODO(developer): Replace these variables before running the sample. + String localFilePath = "path/to/your/video.mp4"; + detectLogo(localFilePath); + } + + public static void detectLogo(String filePath) + 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 (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Read file + Path path = Paths.get(filePath); + byte[] data = Files.readAllBytes(path); + // Create the request + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputContent(ByteString.copyFrom(data)) + .addFeatures(Feature.LOGO_RECOGNITION) + .build(); + + // asynchronously perform object tracking on videos + OperationFuture future = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + // The first result is retrieved because a single video was processed. + AnnotateVideoResponse response = future.get(300, TimeUnit.SECONDS); + VideoAnnotationResults annotationResult = response.getAnnotationResults(0); + + // Annotations for list of logos detected, tracked and recognized in video. + for (LogoRecognitionAnnotation logoRecognitionAnnotation : + annotationResult.getLogoRecognitionAnnotationsList()) { + Entity entity = logoRecognitionAnnotation.getEntity(); + // Opaque entity ID. Some IDs may be available in + // [Google Knowledge Graph Search API](https://developers.google.com/knowledge-graph/). + System.out.printf("Entity Id : %s\n", entity.getEntityId()); + System.out.printf("Description : %s\n", entity.getDescription()); + // All logo tracks where the recognized logo appears. Each track corresponds to one logo + // instance appearing in consecutive frames. + for (Track track : logoRecognitionAnnotation.getTracksList()) { + + // Video segment of a track. + Duration startTimeOffset = track.getSegment().getStartTimeOffset(); + System.out.printf( + "\n\tStart Time Offset: %s.%s\n", + startTimeOffset.getSeconds(), startTimeOffset.getNanos()); + Duration endTimeOffset = track.getSegment().getEndTimeOffset(); + System.out.printf( + "\tEnd Time Offset: %s.%s\n", endTimeOffset.getSeconds(), endTimeOffset.getNanos()); + System.out.printf("\tConfidence: %s\n", track.getConfidence()); + + // The object with timestamp and attributes per frame in the track. + for (TimestampedObject timestampedObject : track.getTimestampedObjectsList()) { + + // Normalized Bounding box in a frame, where the object is located. + NormalizedBoundingBox normalizedBoundingBox = + timestampedObject.getNormalizedBoundingBox(); + System.out.printf("\n\t\tLeft: %s\n", normalizedBoundingBox.getLeft()); + System.out.printf("\t\tTop: %s\n", normalizedBoundingBox.getTop()); + System.out.printf("\t\tRight: %s\n", normalizedBoundingBox.getRight()); + System.out.printf("\t\tBottom: %s\n", normalizedBoundingBox.getBottom()); + + // Optional. The attributes of the object in the bounding box. + for (DetectedAttribute attribute : timestampedObject.getAttributesList()) { + System.out.printf("\n\t\t\tName: %s\n", attribute.getName()); + System.out.printf("\t\t\tConfidence: %s\n", attribute.getConfidence()); + System.out.printf("\t\t\tValue: %s\n", attribute.getValue()); + } + } + + // Optional. Attributes in the track level. + for (DetectedAttribute trackAttribute : track.getAttributesList()) { + System.out.printf("\n\t\tName : %s\n", trackAttribute.getName()); + System.out.printf("\t\tConfidence : %s\n", trackAttribute.getConfidence()); + System.out.printf("\t\tValue : %s\n", trackAttribute.getValue()); + } + } + + // All video segments where the recognized logo appears. There might be multiple instances + // of the same logo class appearing in one VideoSegment. + for (VideoSegment segment : logoRecognitionAnnotation.getSegmentsList()) { + System.out.printf( + "\n\tStart Time Offset : %s.%s\n", + segment.getStartTimeOffset().getSeconds(), segment.getStartTimeOffset().getNanos()); + System.out.printf( + "\tEnd Time Offset : %s.%s\n", + segment.getEndTimeOffset().getSeconds(), segment.getEndTimeOffset().getNanos()); + } + } + } + } +} +// [END video_detect_logo] diff --git a/video/src/main/java/com/example/video/LogoDetectionGcs.java b/video/src/main/java/com/example/video/LogoDetectionGcs.java new file mode 100644 index 00000000000..ee054cfc6fd --- /dev/null +++ b/video/src/main/java/com/example/video/LogoDetectionGcs.java @@ -0,0 +1,134 @@ +/* + * 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.video; + +// [START video_detect_logo_gcs] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.videointelligence.v1.AnnotateVideoProgress; +import com.google.cloud.videointelligence.v1.AnnotateVideoRequest; +import com.google.cloud.videointelligence.v1.AnnotateVideoResponse; +import com.google.cloud.videointelligence.v1.DetectedAttribute; +import com.google.cloud.videointelligence.v1.Entity; +import com.google.cloud.videointelligence.v1.Feature; +import com.google.cloud.videointelligence.v1.LogoRecognitionAnnotation; +import com.google.cloud.videointelligence.v1.NormalizedBoundingBox; +import com.google.cloud.videointelligence.v1.TimestampedObject; +import com.google.cloud.videointelligence.v1.Track; +import com.google.cloud.videointelligence.v1.VideoAnnotationResults; +import com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient; +import com.google.cloud.videointelligence.v1.VideoSegment; +import com.google.protobuf.Duration; +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class LogoDetectionGcs { + + public static void detectLogoGcs() throws Exception { + // TODO(developer): Replace these variables before running the sample. + String gcsUri = "gs://YOUR_BUCKET_ID/path/to/your/video.mp4"; + detectLogoGcs(gcsUri); + } + + public static void detectLogoGcs(String inputUri) + 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 (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Create the request + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputUri(inputUri) + .addFeatures(Feature.LOGO_RECOGNITION) + .build(); + + // asynchronously perform object tracking on videos + OperationFuture future = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + // The first result is retrieved because a single video was processed. + AnnotateVideoResponse response = future.get(300, TimeUnit.SECONDS); + VideoAnnotationResults annotationResult = response.getAnnotationResults(0); + + // Annotations for list of logos detected, tracked and recognized in video. + for (LogoRecognitionAnnotation logoRecognitionAnnotation : + annotationResult.getLogoRecognitionAnnotationsList()) { + Entity entity = logoRecognitionAnnotation.getEntity(); + // Opaque entity ID. Some IDs may be available in + // [Google Knowledge Graph Search API](https://developers.google.com/knowledge-graph/). + System.out.printf("Entity Id : %s\n", entity.getEntityId()); + System.out.printf("Description : %s\n", entity.getDescription()); + // All logo tracks where the recognized logo appears. Each track corresponds to one logo + // instance appearing in consecutive frames. + for (Track track : logoRecognitionAnnotation.getTracksList()) { + + // Video segment of a track. + Duration startTimeOffset = track.getSegment().getStartTimeOffset(); + System.out.printf( + "\n\tStart Time Offset: %s.%s\n", + startTimeOffset.getSeconds(), startTimeOffset.getNanos()); + Duration endTimeOffset = track.getSegment().getEndTimeOffset(); + System.out.printf( + "\tEnd Time Offset: %s.%s\n", endTimeOffset.getSeconds(), endTimeOffset.getNanos()); + System.out.printf("\tConfidence: %s\n", track.getConfidence()); + + // The object with timestamp and attributes per frame in the track. + for (TimestampedObject timestampedObject : track.getTimestampedObjectsList()) { + + // Normalized Bounding box in a frame, where the object is located. + NormalizedBoundingBox normalizedBoundingBox = + timestampedObject.getNormalizedBoundingBox(); + System.out.printf("\n\t\tLeft: %s\n", normalizedBoundingBox.getLeft()); + System.out.printf("\t\tTop: %s\n", normalizedBoundingBox.getTop()); + System.out.printf("\t\tRight: %s\n", normalizedBoundingBox.getRight()); + System.out.printf("\t\tBottom: %s\n", normalizedBoundingBox.getBottom()); + + // Optional. The attributes of the object in the bounding box. + for (DetectedAttribute attribute : timestampedObject.getAttributesList()) { + System.out.printf("\n\t\t\tName: %s\n", attribute.getName()); + System.out.printf("\t\t\tConfidence: %s\n", attribute.getConfidence()); + System.out.printf("\t\t\tValue: %s\n", attribute.getValue()); + } + } + + // Optional. Attributes in the track level. + for (DetectedAttribute trackAttribute : track.getAttributesList()) { + System.out.printf("\n\t\tName : %s\n", trackAttribute.getName()); + System.out.printf("\t\tConfidence : %s\n", trackAttribute.getConfidence()); + System.out.printf("\t\tValue : %s\n", trackAttribute.getValue()); + } + } + + // All video segments where the recognized logo appears. There might be multiple instances + // of the same logo class appearing in one VideoSegment. + for (VideoSegment segment : logoRecognitionAnnotation.getSegmentsList()) { + System.out.printf( + "\n\tStart Time Offset : %s.%s\n", + segment.getStartTimeOffset().getSeconds(), segment.getStartTimeOffset().getNanos()); + System.out.printf( + "\tEnd Time Offset : %s.%s\n", + segment.getEndTimeOffset().getSeconds(), segment.getEndTimeOffset().getNanos()); + } + } + } + } +} +// [END video_detect_logo_gcs] diff --git a/video/src/main/java/com/example/video/QuickstartSample.java b/video/src/main/java/com/example/video/QuickstartSample.java new file mode 100644 index 00000000000..f46d8d84a1f --- /dev/null +++ b/video/src/main/java/com/example/video/QuickstartSample.java @@ -0,0 +1,85 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.video; + +// [START video_quickstart] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.videointelligence.v1.AnnotateVideoProgress; +import com.google.cloud.videointelligence.v1.AnnotateVideoRequest; +import com.google.cloud.videointelligence.v1.AnnotateVideoResponse; +import com.google.cloud.videointelligence.v1.Entity; +import com.google.cloud.videointelligence.v1.Feature; +import com.google.cloud.videointelligence.v1.LabelAnnotation; +import com.google.cloud.videointelligence.v1.LabelSegment; +import com.google.cloud.videointelligence.v1.VideoAnnotationResults; +import com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient; +import java.util.List; + +public class QuickstartSample { + + /** Demonstrates using the video intelligence client to detect labels in a video file. */ + public static void main(String[] args) throws Exception { + // Instantiate a video intelligence client + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // The Google Cloud Storage path to the video to annotate. + String gcsUri = "gs://cloud-samples-data/video/cat.mp4"; + + // Create an operation that will contain the response when the operation completes. + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputUri(gcsUri) + .addFeatures(Feature.LABEL_DETECTION) + .build(); + + OperationFuture response = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + + List results = response.get().getAnnotationResultsList(); + if (results.isEmpty()) { + System.out.println("No labels detected in " + gcsUri); + return; + } + for (VideoAnnotationResults result : results) { + System.out.println("Labels:"); + // get video segment label annotations + for (LabelAnnotation annotation : result.getSegmentLabelAnnotationsList()) { + System.out.println( + "Video label description : " + annotation.getEntity().getDescription()); + // categories + for (Entity categoryEntity : annotation.getCategoryEntitiesList()) { + System.out.println("Label Category description : " + categoryEntity.getDescription()); + } + // segments + for (LabelSegment segment : annotation.getSegmentsList()) { + double startTime = + segment.getSegment().getStartTimeOffset().getSeconds() + + segment.getSegment().getStartTimeOffset().getNanos() / 1e9; + double endTime = + segment.getSegment().getEndTimeOffset().getSeconds() + + segment.getSegment().getEndTimeOffset().getNanos() / 1e9; + System.out.printf("Segment location : %.3f:%.3f\n", startTime, endTime); + System.out.println("Confidence : " + segment.getConfidence()); + } + } + } + } + } +} +// [END video_quickstart] diff --git a/video/src/main/java/com/example/video/TextDetection.java b/video/src/main/java/com/example/video/TextDetection.java new file mode 100644 index 00000000000..dd823298c2a --- /dev/null +++ b/video/src/main/java/com/example/video/TextDetection.java @@ -0,0 +1,174 @@ +/* + * 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.video; + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.videointelligence.v1.AnnotateVideoProgress; +import com.google.cloud.videointelligence.v1.AnnotateVideoRequest; +import com.google.cloud.videointelligence.v1.AnnotateVideoResponse; +import com.google.cloud.videointelligence.v1.Feature; +import com.google.cloud.videointelligence.v1.NormalizedVertex; +import com.google.cloud.videointelligence.v1.TextAnnotation; +import com.google.cloud.videointelligence.v1.TextFrame; +import com.google.cloud.videointelligence.v1.TextSegment; +import com.google.cloud.videointelligence.v1.VideoAnnotationResults; +import com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient; +import com.google.cloud.videointelligence.v1.VideoSegment; +import com.google.protobuf.ByteString; +import com.google.protobuf.Duration; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class TextDetection { + + // [START video_detect_text] + /** + * Detect text in a video. + * + * @param filePath the path to the video file to analyze. + */ + public static VideoAnnotationResults detectText(String filePath) throws Exception { + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Read file + Path path = Paths.get(filePath); + byte[] data = Files.readAllBytes(path); + + // Create the request + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputContent(ByteString.copyFrom(data)) + .addFeatures(Feature.TEXT_DETECTION) + .build(); + + // asynchronously perform object tracking on videos + OperationFuture future = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + // The first result is retrieved because a single video was processed. + AnnotateVideoResponse response = future.get(300, TimeUnit.SECONDS); + VideoAnnotationResults results = response.getAnnotationResults(0); + + // Get only the first annotation for demo purposes. + TextAnnotation annotation = results.getTextAnnotations(0); + System.out.println("Text: " + annotation.getText()); + + // Get the first text segment. + TextSegment textSegment = annotation.getSegments(0); + System.out.println("Confidence: " + textSegment.getConfidence()); + // For the text segment display it's time offset + VideoSegment videoSegment = textSegment.getSegment(); + Duration startTimeOffset = videoSegment.getStartTimeOffset(); + Duration endTimeOffset = videoSegment.getEndTimeOffset(); + // Display the offset times in seconds, 1e9 is part of the formula to convert nanos to seconds + System.out.println( + String.format( + "Start time: %.2f", startTimeOffset.getSeconds() + startTimeOffset.getNanos() / 1e9)); + System.out.println( + String.format( + "End time: %.2f", endTimeOffset.getSeconds() + endTimeOffset.getNanos() / 1e9)); + + // Show the first result for the first frame in the segment. + TextFrame textFrame = textSegment.getFrames(0); + Duration timeOffset = textFrame.getTimeOffset(); + System.out.println( + String.format( + "Time offset for the first frame: %.2f", + timeOffset.getSeconds() + timeOffset.getNanos() / 1e9)); + + // Display the rotated bounding box for where the text is on the frame. + System.out.println("Rotated Bounding Box Vertices:"); + List vertices = textFrame.getRotatedBoundingBox().getVerticesList(); + for (NormalizedVertex normalizedVertex : vertices) { + System.out.println( + String.format( + "\tVertex.x: %.2f, Vertex.y: %.2f", + normalizedVertex.getX(), normalizedVertex.getY())); + } + return results; + } + } + // [END video_detect_text] + + // [START video_detect_text_gcs] + /** + * Detect Text in a video. + * + * @param gcsUri the path to the video file to analyze. + */ + public static VideoAnnotationResults detectTextGcs(String gcsUri) throws Exception { + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Create the request + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputUri(gcsUri) + .addFeatures(Feature.TEXT_DETECTION) + .build(); + + // asynchronously perform object tracking on videos + OperationFuture future = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + // The first result is retrieved because a single video was processed. + AnnotateVideoResponse response = future.get(300, TimeUnit.SECONDS); + VideoAnnotationResults results = response.getAnnotationResults(0); + + // Get only the first annotation for demo purposes. + TextAnnotation annotation = results.getTextAnnotations(0); + System.out.println("Text: " + annotation.getText()); + + // Get the first text segment. + TextSegment textSegment = annotation.getSegments(0); + System.out.println("Confidence: " + textSegment.getConfidence()); + // For the text segment display it's time offset + VideoSegment videoSegment = textSegment.getSegment(); + Duration startTimeOffset = videoSegment.getStartTimeOffset(); + Duration endTimeOffset = videoSegment.getEndTimeOffset(); + // Display the offset times in seconds, 1e9 is part of the formula to convert nanos to seconds + System.out.println( + String.format( + "Start time: %.2f", startTimeOffset.getSeconds() + startTimeOffset.getNanos() / 1e9)); + System.out.println( + String.format( + "End time: %.2f", endTimeOffset.getSeconds() + endTimeOffset.getNanos() / 1e9)); + + // Show the first result for the first frame in the segment. + TextFrame textFrame = textSegment.getFrames(0); + Duration timeOffset = textFrame.getTimeOffset(); + System.out.println( + String.format( + "Time offset for the first frame: %.2f", + timeOffset.getSeconds() + timeOffset.getNanos() / 1e9)); + + // Display the rotated bounding box for where the text is on the frame. + System.out.println("Rotated Bounding Box Vertices:"); + List vertices = textFrame.getRotatedBoundingBox().getVerticesList(); + for (NormalizedVertex normalizedVertex : vertices) { + System.out.println( + String.format( + "\tVertex.x: %.2f, Vertex.y: %.2f", + normalizedVertex.getX(), normalizedVertex.getY())); + } + return results; + } + } + // [END video_detect_text_gcs] +} diff --git a/video/src/main/java/com/example/video/TrackObjects.java b/video/src/main/java/com/example/video/TrackObjects.java new file mode 100644 index 00000000000..d9c43df8866 --- /dev/null +++ b/video/src/main/java/com/example/video/TrackObjects.java @@ -0,0 +1,179 @@ +/* + * 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.video; + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.videointelligence.v1.AnnotateVideoProgress; +import com.google.cloud.videointelligence.v1.AnnotateVideoRequest; +import com.google.cloud.videointelligence.v1.AnnotateVideoResponse; +import com.google.cloud.videointelligence.v1.Entity; +import com.google.cloud.videointelligence.v1.Feature; +import com.google.cloud.videointelligence.v1.NormalizedBoundingBox; +import com.google.cloud.videointelligence.v1.ObjectTrackingAnnotation; +import com.google.cloud.videointelligence.v1.ObjectTrackingFrame; +import com.google.cloud.videointelligence.v1.VideoAnnotationResults; +import com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient; +import com.google.cloud.videointelligence.v1.VideoSegment; +import com.google.protobuf.ByteString; +import com.google.protobuf.Duration; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.TimeUnit; + +public class TrackObjects { + + // [START video_object_tracking] + /** + * Track objects in a video. + * + * @param filePath the path to the video file to analyze. + */ + public static VideoAnnotationResults trackObjects(String filePath) throws Exception { + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Read file + Path path = Paths.get(filePath); + byte[] data = Files.readAllBytes(path); + + // Create the request + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputContent(ByteString.copyFrom(data)) + .addFeatures(Feature.OBJECT_TRACKING) + .setLocationId("us-east1") + .build(); + + // asynchronously perform object tracking on videos + OperationFuture future = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + // The first result is retrieved because a single video was processed. + AnnotateVideoResponse response = future.get(450, TimeUnit.SECONDS); + VideoAnnotationResults results = response.getAnnotationResults(0); + + // Get only the first annotation for demo purposes. + ObjectTrackingAnnotation annotation = results.getObjectAnnotations(0); + System.out.println("Confidence: " + annotation.getConfidence()); + + if (annotation.hasEntity()) { + Entity entity = annotation.getEntity(); + System.out.println("Entity description: " + entity.getDescription()); + System.out.println("Entity id:: " + entity.getEntityId()); + } + + if (annotation.hasSegment()) { + VideoSegment videoSegment = annotation.getSegment(); + Duration startTimeOffset = videoSegment.getStartTimeOffset(); + Duration endTimeOffset = videoSegment.getEndTimeOffset(); + // Display the segment time in seconds, 1e9 converts nanos to seconds + System.out.println( + String.format( + "Segment: %.2fs to %.2fs", + startTimeOffset.getSeconds() + startTimeOffset.getNanos() / 1e9, + endTimeOffset.getSeconds() + endTimeOffset.getNanos() / 1e9)); + } + + // Here we print only the bounding box of the first frame in this segment. + ObjectTrackingFrame frame = annotation.getFrames(0); + // Display the offset time in seconds, 1e9 converts nanos to seconds + Duration timeOffset = frame.getTimeOffset(); + System.out.println( + String.format( + "Time offset of the first frame: %.2fs", + timeOffset.getSeconds() + timeOffset.getNanos() / 1e9)); + + // Display the bounding box of the detected object + NormalizedBoundingBox normalizedBoundingBox = frame.getNormalizedBoundingBox(); + System.out.println("Bounding box position:"); + System.out.println("\tleft: " + normalizedBoundingBox.getLeft()); + System.out.println("\ttop: " + normalizedBoundingBox.getTop()); + System.out.println("\tright: " + normalizedBoundingBox.getRight()); + System.out.println("\tbottom: " + normalizedBoundingBox.getBottom()); + return results; + } + } + // [END video_object_tracking] + + // [START video_object_tracking_gcs] + /** + * Track objects in a video. + * + * @param gcsUri the path to the video file to analyze. + */ + public static VideoAnnotationResults trackObjectsGcs(String gcsUri) throws Exception { + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Create the request + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputUri(gcsUri) + .addFeatures(Feature.OBJECT_TRACKING) + .setLocationId("us-east1") + .build(); + + // asynchronously perform object tracking on videos + OperationFuture future = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + // The first result is retrieved because a single video was processed. + AnnotateVideoResponse response = future.get(300, TimeUnit.SECONDS); + VideoAnnotationResults results = response.getAnnotationResults(0); + + // Get only the first annotation for demo purposes. + ObjectTrackingAnnotation annotation = results.getObjectAnnotations(0); + System.out.println("Confidence: " + annotation.getConfidence()); + + if (annotation.hasEntity()) { + Entity entity = annotation.getEntity(); + System.out.println("Entity description: " + entity.getDescription()); + System.out.println("Entity id:: " + entity.getEntityId()); + } + + if (annotation.hasSegment()) { + VideoSegment videoSegment = annotation.getSegment(); + Duration startTimeOffset = videoSegment.getStartTimeOffset(); + Duration endTimeOffset = videoSegment.getEndTimeOffset(); + // Display the segment time in seconds, 1e9 converts nanos to seconds + System.out.println( + String.format( + "Segment: %.2fs to %.2fs", + startTimeOffset.getSeconds() + startTimeOffset.getNanos() / 1e9, + endTimeOffset.getSeconds() + endTimeOffset.getNanos() / 1e9)); + } + + // Here we print only the bounding box of the first frame in this segment. + ObjectTrackingFrame frame = annotation.getFrames(0); + // Display the offset time in seconds, 1e9 converts nanos to seconds + Duration timeOffset = frame.getTimeOffset(); + System.out.println( + String.format( + "Time offset of the first frame: %.2fs", + timeOffset.getSeconds() + timeOffset.getNanos() / 1e9)); + + // Display the bounding box of the detected object + NormalizedBoundingBox normalizedBoundingBox = frame.getNormalizedBoundingBox(); + System.out.println("Bounding box position:"); + System.out.println("\tleft: " + normalizedBoundingBox.getLeft()); + System.out.println("\ttop: " + normalizedBoundingBox.getTop()); + System.out.println("\tright: " + normalizedBoundingBox.getRight()); + System.out.println("\tbottom: " + normalizedBoundingBox.getBottom()); + return results; + } + } + // [END video_object_tracking_gcs] +} diff --git a/video/src/main/java/video/Detect.java b/video/src/main/java/video/Detect.java new file mode 100644 index 00000000000..40e08f5361b --- /dev/null +++ b/video/src/main/java/video/Detect.java @@ -0,0 +1,410 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package video; + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.videointelligence.v1.AnnotateVideoProgress; +import com.google.cloud.videointelligence.v1.AnnotateVideoRequest; +import com.google.cloud.videointelligence.v1.AnnotateVideoResponse; +import com.google.cloud.videointelligence.v1.Entity; +import com.google.cloud.videointelligence.v1.ExplicitContentFrame; +import com.google.cloud.videointelligence.v1.Feature; +import com.google.cloud.videointelligence.v1.LabelAnnotation; +import com.google.cloud.videointelligence.v1.LabelSegment; +import com.google.cloud.videointelligence.v1.SpeechRecognitionAlternative; +import com.google.cloud.videointelligence.v1.SpeechTranscription; +import com.google.cloud.videointelligence.v1.SpeechTranscriptionConfig; +import com.google.cloud.videointelligence.v1.VideoAnnotationResults; +import com.google.cloud.videointelligence.v1.VideoContext; +import com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient; +import com.google.cloud.videointelligence.v1.VideoSegment; +import com.google.cloud.videointelligence.v1.WordInfo; +import com.google.protobuf.ByteString; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.TimeUnit; + +public class Detect { + /** + * Detects labels, shots, and explicit content in a video using the Video Intelligence API + * + * @param args specifies features to detect and the path to the video on Google Cloud Storage. + */ + public static void main(String[] args) { + try { + argsHelper(args); + } catch (Exception e) { + System.out.println("Exception while running:\n" + e.getMessage() + "\n"); + e.printStackTrace(System.out); + } + } + + /** + * Helper that handles the input passed to the program. + * + * @param args specifies features to detect and the path to the video on Google Cloud Storage. + * @throws IOException on Input/Output errors. + */ + public static void argsHelper(String[] args) throws Exception { + if (args.length < 1) { + System.out.println("Usage:"); + System.out.printf( + "\tjava %s \"\" \"\"\n" + + "Commands:\n" + + "\tlabels | shots\n" + + "Path:\n\tA URI for a Cloud Storage resource (gs://...)\n" + + "Examples: ", + Detect.class.getCanonicalName()); + return; + } + String command = args[0]; + String path = args.length > 1 ? args[1] : ""; + + if (command.equals("labels")) { + analyzeLabels(path); + } + if (command.equals("labels-file")) { + analyzeLabelsFile(path); + } + if (command.equals("shots")) { + analyzeShots(path); + } + if (command.equals("explicit-content")) { + analyzeExplicitContent(path); + } + if (command.equals("speech-transcription")) { + speechTranscription(path); + } + } + + /** + * Performs label analysis on the video at the provided Cloud Storage path. + * + * @param gcsUri the path to the video file to analyze. + */ + public static void analyzeLabels(String gcsUri) throws Exception { + // [START video_analyze_labels_gcs] + // Instantiate a com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Provide path to file hosted on GCS as "gs://bucket-name/..." + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputUri(gcsUri) + .addFeatures(Feature.LABEL_DETECTION) + .build(); + // Create an operation that will contain the response when the operation completes. + OperationFuture response = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + for (VideoAnnotationResults results : response.get().getAnnotationResultsList()) { + // process video / segment level label annotations + System.out.println("Locations: "); + for (LabelAnnotation labelAnnotation : results.getSegmentLabelAnnotationsList()) { + System.out.println("Video label: " + labelAnnotation.getEntity().getDescription()); + // categories + for (Entity categoryEntity : labelAnnotation.getCategoryEntitiesList()) { + System.out.println("Video label category: " + categoryEntity.getDescription()); + } + // segments + for (LabelSegment segment : labelAnnotation.getSegmentsList()) { + double startTime = + segment.getSegment().getStartTimeOffset().getSeconds() + + segment.getSegment().getStartTimeOffset().getNanos() / 1e9; + double endTime = + segment.getSegment().getEndTimeOffset().getSeconds() + + segment.getSegment().getEndTimeOffset().getNanos() / 1e9; + System.out.printf("Segment location: %.3f:%.3f\n", startTime, endTime); + System.out.println("Confidence: " + segment.getConfidence()); + } + } + + // process shot label annotations + for (LabelAnnotation labelAnnotation : results.getShotLabelAnnotationsList()) { + System.out.println("Shot label: " + labelAnnotation.getEntity().getDescription()); + // categories + for (Entity categoryEntity : labelAnnotation.getCategoryEntitiesList()) { + System.out.println("Shot label category: " + categoryEntity.getDescription()); + } + // segments + for (LabelSegment segment : labelAnnotation.getSegmentsList()) { + double startTime = + segment.getSegment().getStartTimeOffset().getSeconds() + + segment.getSegment().getStartTimeOffset().getNanos() / 1e9; + double endTime = + segment.getSegment().getEndTimeOffset().getSeconds() + + segment.getSegment().getEndTimeOffset().getNanos() / 1e9; + System.out.printf("Segment location: %.3f:%.3f\n", startTime, endTime); + System.out.println("Confidence: " + segment.getConfidence()); + } + } + + // process frame label annotations + for (LabelAnnotation labelAnnotation : results.getFrameLabelAnnotationsList()) { + System.out.println("Frame label: " + labelAnnotation.getEntity().getDescription()); + // categories + for (Entity categoryEntity : labelAnnotation.getCategoryEntitiesList()) { + System.out.println("Frame label category: " + categoryEntity.getDescription()); + } + // segments + for (LabelSegment segment : labelAnnotation.getSegmentsList()) { + double startTime = + segment.getSegment().getStartTimeOffset().getSeconds() + + segment.getSegment().getStartTimeOffset().getNanos() / 1e9; + double endTime = + segment.getSegment().getEndTimeOffset().getSeconds() + + segment.getSegment().getEndTimeOffset().getNanos() / 1e9; + System.out.printf("Segment location: %.3f:%.2f\n", startTime, endTime); + System.out.println("Confidence: " + segment.getConfidence()); + } + } + } + } + // [END video_analyze_labels_gcs] + } + + /** + * Performs label analysis on the video at the provided file path. + * + * @param filePath the path to the video file to analyze. + */ + public static void analyzeLabelsFile(String filePath) throws Exception { + // [START video_analyze_labels] + // Instantiate a com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Read file and encode into Base64 + Path path = Paths.get(filePath); + byte[] data = Files.readAllBytes(path); + + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputContent(ByteString.copyFrom(data)) + .addFeatures(Feature.LABEL_DETECTION) + .build(); + // Create an operation that will contain the response when the operation completes. + OperationFuture response = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + for (VideoAnnotationResults results : response.get().getAnnotationResultsList()) { + // process video / segment level label annotations + System.out.println("Locations: "); + for (LabelAnnotation labelAnnotation : results.getSegmentLabelAnnotationsList()) { + System.out.println("Video label: " + labelAnnotation.getEntity().getDescription()); + // categories + for (Entity categoryEntity : labelAnnotation.getCategoryEntitiesList()) { + System.out.println("Video label category: " + categoryEntity.getDescription()); + } + // segments + for (LabelSegment segment : labelAnnotation.getSegmentsList()) { + double startTime = + segment.getSegment().getStartTimeOffset().getSeconds() + + segment.getSegment().getStartTimeOffset().getNanos() / 1e9; + double endTime = + segment.getSegment().getEndTimeOffset().getSeconds() + + segment.getSegment().getEndTimeOffset().getNanos() / 1e9; + System.out.printf("Segment location: %.3f:%.2f\n", startTime, endTime); + System.out.println("Confidence: " + segment.getConfidence()); + } + } + + // process shot label annotations + for (LabelAnnotation labelAnnotation : results.getShotLabelAnnotationsList()) { + System.out.println("Shot label: " + labelAnnotation.getEntity().getDescription()); + // categories + for (Entity categoryEntity : labelAnnotation.getCategoryEntitiesList()) { + System.out.println("Shot label category: " + categoryEntity.getDescription()); + } + // segments + for (LabelSegment segment : labelAnnotation.getSegmentsList()) { + double startTime = + segment.getSegment().getStartTimeOffset().getSeconds() + + segment.getSegment().getStartTimeOffset().getNanos() / 1e9; + double endTime = + segment.getSegment().getEndTimeOffset().getSeconds() + + segment.getSegment().getEndTimeOffset().getNanos() / 1e9; + System.out.printf("Segment location: %.3f:%.2f\n", startTime, endTime); + System.out.println("Confidence: " + segment.getConfidence()); + } + } + + // process frame label annotations + for (LabelAnnotation labelAnnotation : results.getFrameLabelAnnotationsList()) { + System.out.println("Frame label: " + labelAnnotation.getEntity().getDescription()); + // categories + for (Entity categoryEntity : labelAnnotation.getCategoryEntitiesList()) { + System.out.println("Frame label category: " + categoryEntity.getDescription()); + } + // segments + for (LabelSegment segment : labelAnnotation.getSegmentsList()) { + double startTime = + segment.getSegment().getStartTimeOffset().getSeconds() + + segment.getSegment().getStartTimeOffset().getNanos() / 1e9; + double endTime = + segment.getSegment().getEndTimeOffset().getSeconds() + + segment.getSegment().getEndTimeOffset().getNanos() / 1e9; + System.out.printf("Segment location: %.3f:%.2f\n", startTime, endTime); + System.out.println("Confidence: " + segment.getConfidence()); + } + } + } + } + // [END video_analyze_labels] + } + + /** + * Performs shot analysis on the video at the provided Cloud Storage path. + * + * @param gcsUri the path to the video file to analyze. + */ + public static void analyzeShots(String gcsUri) throws Exception { + // [START video_analyze_shots] + // Instantiate a com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Provide path to file hosted on GCS as "gs://bucket-name/..." + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputUri(gcsUri) + .addFeatures(Feature.SHOT_CHANGE_DETECTION) + .build(); + + // Create an operation that will contain the response when the operation completes. + OperationFuture response = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + // Print detected shot changes and their location ranges in the analyzed video. + for (VideoAnnotationResults result : response.get().getAnnotationResultsList()) { + if (result.getShotAnnotationsCount() > 0) { + System.out.println("Shots: "); + for (VideoSegment segment : result.getShotAnnotationsList()) { + double startTime = + segment.getStartTimeOffset().getSeconds() + + segment.getStartTimeOffset().getNanos() / 1e9; + double endTime = + segment.getEndTimeOffset().getSeconds() + + segment.getEndTimeOffset().getNanos() / 1e9; + System.out.printf("Location: %.3f:%.3f\n", startTime, endTime); + } + } else { + System.out.println("No shot changes detected in " + gcsUri); + } + } + } + // [END video_analyze_shots] + } + + /** + * Performs explicit content analysis on the video at the provided Cloud Storage path. + * + * @param gcsUri the path to the video file to analyze. + */ + public static void analyzeExplicitContent(String gcsUri) throws Exception { + // [START video_analyze_explicit_content] + // Instantiate a com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Create an operation that will contain the response when the operation completes. + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputUri(gcsUri) + .addFeatures(Feature.EXPLICIT_CONTENT_DETECTION) + .build(); + + OperationFuture response = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + // Print detected annotations and their positions in the analyzed video. + for (VideoAnnotationResults result : response.get().getAnnotationResultsList()) { + for (ExplicitContentFrame frame : result.getExplicitAnnotation().getFramesList()) { + double frameTime = + frame.getTimeOffset().getSeconds() + frame.getTimeOffset().getNanos() / 1e9; + System.out.printf("Location: %.3fs\n", frameTime); + System.out.println("Adult: " + frame.getPornographyLikelihood()); + } + } + // [END video_analyze_explicit_content] + } + } + + /** + * Transcribe speech from a video stored on GCS. + * + * @param gcsUri the path to the video file to analyze. + */ + public static void speechTranscription(String gcsUri) throws Exception { + // [START video_speech_transcription_gcs] + // Instantiate a com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Set the language code + SpeechTranscriptionConfig config = + SpeechTranscriptionConfig.newBuilder() + .setLanguageCode("en-US") + .setEnableAutomaticPunctuation(true) + .build(); + + // Set the video context with the above configuration + VideoContext context = VideoContext.newBuilder().setSpeechTranscriptionConfig(config).build(); + + // Create the request + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputUri(gcsUri) + .addFeatures(Feature.SPEECH_TRANSCRIPTION) + .setVideoContext(context) + .build(); + + // asynchronously perform speech transcription on videos + OperationFuture response = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + // Display the results + for (VideoAnnotationResults results : + response.get(600, TimeUnit.SECONDS).getAnnotationResultsList()) { + for (SpeechTranscription speechTranscription : results.getSpeechTranscriptionsList()) { + try { + // Print the transcription + if (speechTranscription.getAlternativesCount() > 0) { + SpeechRecognitionAlternative alternative = speechTranscription.getAlternatives(0); + + System.out.printf("Transcript: %s\n", alternative.getTranscript()); + System.out.printf("Confidence: %.2f\n", alternative.getConfidence()); + + System.out.println("Word level information:"); + for (WordInfo wordInfo : alternative.getWordsList()) { + double startTime = + wordInfo.getStartTime().getSeconds() + wordInfo.getStartTime().getNanos() / 1e9; + double endTime = + wordInfo.getEndTime().getSeconds() + wordInfo.getEndTime().getNanos() / 1e9; + System.out.printf( + "\t%4.2fs - %4.2fs: %s\n", startTime, endTime, wordInfo.getWord()); + } + } else { + System.out.println("No transcription found"); + } + } catch (IndexOutOfBoundsException ioe) { + System.out.println("Could not retrieve frame: " + ioe.getMessage()); + } + } + } + } + // [END video_speech_transcription_gcs] + } +} diff --git a/video/src/main/java/video/DetectFaces.java b/video/src/main/java/video/DetectFaces.java new file mode 100644 index 00000000000..71e45aec93f --- /dev/null +++ b/video/src/main/java/video/DetectFaces.java @@ -0,0 +1,112 @@ +/* + * 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 video; + +// [START video_detect_faces] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.videointelligence.v1.AnnotateVideoProgress; +import com.google.cloud.videointelligence.v1.AnnotateVideoRequest; +import com.google.cloud.videointelligence.v1.AnnotateVideoResponse; +import com.google.cloud.videointelligence.v1.DetectedAttribute; +import com.google.cloud.videointelligence.v1.FaceDetectionAnnotation; +import com.google.cloud.videointelligence.v1.FaceDetectionConfig; +import com.google.cloud.videointelligence.v1.Feature; +import com.google.cloud.videointelligence.v1.TimestampedObject; +import com.google.cloud.videointelligence.v1.Track; +import com.google.cloud.videointelligence.v1.VideoAnnotationResults; +import com.google.cloud.videointelligence.v1.VideoContext; +import com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient; +import com.google.cloud.videointelligence.v1.VideoSegment; +import com.google.protobuf.ByteString; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class DetectFaces { + + public static void detectFaces() throws Exception { + // TODO(developer): Replace these variables before running the sample. + String localFilePath = "resources/googlework_short.mp4"; + detectFaces(localFilePath); + } + + // Detects faces in a video stored in a local file using the Cloud Video Intelligence API. + public static void detectFaces(String localFilePath) throws Exception { + try (VideoIntelligenceServiceClient videoIntelligenceServiceClient = + VideoIntelligenceServiceClient.create()) { + // Reads a local video file and converts it to base64. + Path path = Paths.get(localFilePath); + byte[] data = Files.readAllBytes(path); + ByteString inputContent = ByteString.copyFrom(data); + + FaceDetectionConfig faceDetectionConfig = + FaceDetectionConfig.newBuilder() + // Must set includeBoundingBoxes to true to get facial attributes. + .setIncludeBoundingBoxes(true) + .setIncludeAttributes(true) + .build(); + VideoContext videoContext = + VideoContext.newBuilder().setFaceDetectionConfig(faceDetectionConfig).build(); + + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputContent(inputContent) + .addFeatures(Feature.FACE_DETECTION) + .setVideoContext(videoContext) + .build(); + + // Detects faces in a video + OperationFuture future = + videoIntelligenceServiceClient.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + AnnotateVideoResponse response = future.get(); + + // Gets annotations for video + VideoAnnotationResults annotationResult = response.getAnnotationResultsList().get(0); + + // Annotations for list of faces detected, tracked and recognized in video. + for (FaceDetectionAnnotation faceDetectionAnnotation : + annotationResult.getFaceDetectionAnnotationsList()) { + System.out.print("Face detected:\n"); + for (Track track : faceDetectionAnnotation.getTracksList()) { + VideoSegment segment = track.getSegment(); + System.out.printf( + "\tStart: %d.%.0fs\n", + segment.getStartTimeOffset().getSeconds(), + segment.getStartTimeOffset().getNanos() / 1e6); + System.out.printf( + "\tEnd: %d.%.0fs\n", + segment.getEndTimeOffset().getSeconds(), segment.getEndTimeOffset().getNanos() / 1e6); + + // Each segment includes timestamped objects that + // include characteristics of the face detected. + TimestampedObject firstTimestampedObject = track.getTimestampedObjects(0); + + for (DetectedAttribute attribute : firstTimestampedObject.getAttributesList()) { + // Attributes include glasses, headwear, smiling, direction of gaze + System.out.printf( + "\tAttribute %s: %s %s\n", + attribute.getName(), attribute.getValue(), attribute.getConfidence()); + } + } + } + } + } +} +// [END video_detect_faces] diff --git a/video/src/main/java/video/DetectFacesGcs.java b/video/src/main/java/video/DetectFacesGcs.java new file mode 100644 index 00000000000..ec41d6d572b --- /dev/null +++ b/video/src/main/java/video/DetectFacesGcs.java @@ -0,0 +1,104 @@ +/* + * 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 video; + +// [START video_detect_faces_gcs] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.videointelligence.v1.AnnotateVideoProgress; +import com.google.cloud.videointelligence.v1.AnnotateVideoRequest; +import com.google.cloud.videointelligence.v1.AnnotateVideoResponse; +import com.google.cloud.videointelligence.v1.DetectedAttribute; +import com.google.cloud.videointelligence.v1.FaceDetectionAnnotation; +import com.google.cloud.videointelligence.v1.FaceDetectionConfig; +import com.google.cloud.videointelligence.v1.Feature; +import com.google.cloud.videointelligence.v1.TimestampedObject; +import com.google.cloud.videointelligence.v1.Track; +import com.google.cloud.videointelligence.v1.VideoAnnotationResults; +import com.google.cloud.videointelligence.v1.VideoContext; +import com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient; +import com.google.cloud.videointelligence.v1.VideoSegment; + +public class DetectFacesGcs { + + public static void detectFacesGcs() throws Exception { + // TODO(developer): Replace these variables before running the sample. + String gcsUri = "gs://cloud-samples-data/video/googlework_short.mp4"; + detectFacesGcs(gcsUri); + } + + // Detects faces in a video stored in Google Cloud Storage using the Cloud Video Intelligence API. + public static void detectFacesGcs(String gcsUri) throws Exception { + try (VideoIntelligenceServiceClient videoIntelligenceServiceClient = + VideoIntelligenceServiceClient.create()) { + + FaceDetectionConfig faceDetectionConfig = + FaceDetectionConfig.newBuilder() + // Must set includeBoundingBoxes to true to get facial attributes. + .setIncludeBoundingBoxes(true) + .setIncludeAttributes(true) + .build(); + VideoContext videoContext = + VideoContext.newBuilder().setFaceDetectionConfig(faceDetectionConfig).build(); + + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputUri(gcsUri) + .addFeatures(Feature.FACE_DETECTION) + .setVideoContext(videoContext) + .build(); + + // Detects faces in a video + OperationFuture future = + videoIntelligenceServiceClient.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + AnnotateVideoResponse response = future.get(); + + // Gets annotations for video + VideoAnnotationResults annotationResult = response.getAnnotationResultsList().get(0); + + // Annotations for list of people detected, tracked and recognized in video. + for (FaceDetectionAnnotation faceDetectionAnnotation : + annotationResult.getFaceDetectionAnnotationsList()) { + System.out.print("Face detected:\n"); + for (Track track : faceDetectionAnnotation.getTracksList()) { + VideoSegment segment = track.getSegment(); + System.out.printf( + "\tStart: %d.%.0fs\n", + segment.getStartTimeOffset().getSeconds(), + segment.getStartTimeOffset().getNanos() / 1e6); + System.out.printf( + "\tEnd: %d.%.0fs\n", + segment.getEndTimeOffset().getSeconds(), segment.getEndTimeOffset().getNanos() / 1e6); + + // Each segment includes timestamped objects that + // include characteristics of the face detected. + TimestampedObject firstTimestampedObject = track.getTimestampedObjects(0); + + for (DetectedAttribute attribute : firstTimestampedObject.getAttributesList()) { + // Attributes include glasses, headwear, smiling, direction of gaze + System.out.printf( + "\tAttribute %s: %s %s\n", + attribute.getName(), attribute.getValue(), attribute.getConfidence()); + } + } + } + } + } +} +// [END video_detect_faces_gcs] diff --git a/video/src/main/java/video/DetectPerson.java b/video/src/main/java/video/DetectPerson.java new file mode 100644 index 00000000000..29653db79d1 --- /dev/null +++ b/video/src/main/java/video/DetectPerson.java @@ -0,0 +1,122 @@ +/* + * 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 video; + +// [START video_detect_person] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.videointelligence.v1.AnnotateVideoProgress; +import com.google.cloud.videointelligence.v1.AnnotateVideoRequest; +import com.google.cloud.videointelligence.v1.AnnotateVideoResponse; +import com.google.cloud.videointelligence.v1.DetectedAttribute; +import com.google.cloud.videointelligence.v1.DetectedLandmark; +import com.google.cloud.videointelligence.v1.Feature; +import com.google.cloud.videointelligence.v1.PersonDetectionAnnotation; +import com.google.cloud.videointelligence.v1.PersonDetectionConfig; +import com.google.cloud.videointelligence.v1.TimestampedObject; +import com.google.cloud.videointelligence.v1.Track; +import com.google.cloud.videointelligence.v1.VideoAnnotationResults; +import com.google.cloud.videointelligence.v1.VideoContext; +import com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient; +import com.google.cloud.videointelligence.v1.VideoSegment; +import com.google.protobuf.ByteString; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class DetectPerson { + + public static void detectPerson() throws Exception { + // TODO(developer): Replace these variables before running the sample. + String localFilePath = "resources/googlework_short.mp4"; + detectPerson(localFilePath); + } + + // Detects people in a video stored in a local file using the Cloud Video Intelligence API. + public static void detectPerson(String localFilePath) throws Exception { + try (VideoIntelligenceServiceClient videoIntelligenceServiceClient = + VideoIntelligenceServiceClient.create()) { + // Reads a local video file and converts it to base64. + Path path = Paths.get(localFilePath); + byte[] data = Files.readAllBytes(path); + ByteString inputContent = ByteString.copyFrom(data); + + PersonDetectionConfig personDetectionConfig = + PersonDetectionConfig.newBuilder() + // Must set includeBoundingBoxes to true to get poses and attributes. + .setIncludeBoundingBoxes(true) + .setIncludePoseLandmarks(true) + .setIncludeAttributes(true) + .build(); + VideoContext videoContext = + VideoContext.newBuilder().setPersonDetectionConfig(personDetectionConfig).build(); + + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputContent(inputContent) + .addFeatures(Feature.PERSON_DETECTION) + .setVideoContext(videoContext) + .build(); + + // Detects people in a video + // We get the first result because only one video is processed. + OperationFuture future = + videoIntelligenceServiceClient.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + AnnotateVideoResponse response = future.get(); + + // Gets annotations for video + VideoAnnotationResults annotationResult = response.getAnnotationResultsList().get(0); + + // Annotations for list of people detected, tracked and recognized in video. + for (PersonDetectionAnnotation personDetectionAnnotation : + annotationResult.getPersonDetectionAnnotationsList()) { + System.out.print("Person detected:\n"); + for (Track track : personDetectionAnnotation.getTracksList()) { + VideoSegment segment = track.getSegment(); + System.out.printf( + "\tStart: %d.%.0fs\n", + segment.getStartTimeOffset().getSeconds(), + segment.getStartTimeOffset().getNanos() / 1e6); + System.out.printf( + "\tEnd: %d.%.0fs\n", + segment.getEndTimeOffset().getSeconds(), segment.getEndTimeOffset().getNanos() / 1e6); + + // Each segment includes timestamped objects that include characteristic--e.g. clothes, + // posture of the person detected. + TimestampedObject firstTimestampedObject = track.getTimestampedObjects(0); + + // Attributes include unique pieces of clothing, poses (i.e., body landmarks) + // of the person detected. + for (DetectedAttribute attribute : firstTimestampedObject.getAttributesList()) { + System.out.printf( + "\tAttribute: %s; Value: %s\n", attribute.getName(), attribute.getValue()); + } + + // Landmarks in person detection include body parts. + for (DetectedLandmark attribute : firstTimestampedObject.getLandmarksList()) { + System.out.printf( + "\tLandmark: %s; Vertex: %f, %f\n", + attribute.getName(), attribute.getPoint().getX(), attribute.getPoint().getY()); + } + } + } + } + } +} +// [END video_detect_person] diff --git a/video/src/main/java/video/DetectPersonGcs.java b/video/src/main/java/video/DetectPersonGcs.java new file mode 100644 index 00000000000..df4fdd07824 --- /dev/null +++ b/video/src/main/java/video/DetectPersonGcs.java @@ -0,0 +1,114 @@ +/* + * 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 video; + +// [START video_detect_person_gcs] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.videointelligence.v1.AnnotateVideoProgress; +import com.google.cloud.videointelligence.v1.AnnotateVideoRequest; +import com.google.cloud.videointelligence.v1.AnnotateVideoResponse; +import com.google.cloud.videointelligence.v1.DetectedAttribute; +import com.google.cloud.videointelligence.v1.DetectedLandmark; +import com.google.cloud.videointelligence.v1.Feature; +import com.google.cloud.videointelligence.v1.PersonDetectionAnnotation; +import com.google.cloud.videointelligence.v1.PersonDetectionConfig; +import com.google.cloud.videointelligence.v1.TimestampedObject; +import com.google.cloud.videointelligence.v1.Track; +import com.google.cloud.videointelligence.v1.VideoAnnotationResults; +import com.google.cloud.videointelligence.v1.VideoContext; +import com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient; +import com.google.cloud.videointelligence.v1.VideoSegment; + +public class DetectPersonGcs { + + public static void detectPersonGcs() throws Exception { + // TODO(developer): Replace these variables before running the sample. + String gcsUri = "gs://cloud-samples-data/video/googlework_short.mp4"; + detectPersonGcs(gcsUri); + } + + // Detects people in a video stored in Google Cloud Storage using + // the Cloud Video Intelligence API. + public static void detectPersonGcs(String gcsUri) throws Exception { + try (VideoIntelligenceServiceClient videoIntelligenceServiceClient = + VideoIntelligenceServiceClient.create()) { + // Reads a local video file and converts it to base64. + + PersonDetectionConfig personDetectionConfig = + PersonDetectionConfig.newBuilder() + // Must set includeBoundingBoxes to true to get poses and attributes. + .setIncludeBoundingBoxes(true) + .setIncludePoseLandmarks(true) + .setIncludeAttributes(true) + .build(); + VideoContext videoContext = + VideoContext.newBuilder().setPersonDetectionConfig(personDetectionConfig).build(); + + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputUri(gcsUri) + .addFeatures(Feature.PERSON_DETECTION) + .setVideoContext(videoContext) + .build(); + + // Detects people in a video + OperationFuture future = + videoIntelligenceServiceClient.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + AnnotateVideoResponse response = future.get(); + // Get the first response, since we sent only one video. + VideoAnnotationResults annotationResult = response.getAnnotationResultsList().get(0); + + // Annotations for list of people detected, tracked and recognized in video. + for (PersonDetectionAnnotation personDetectionAnnotation : + annotationResult.getPersonDetectionAnnotationsList()) { + System.out.print("Person detected:\n"); + for (Track track : personDetectionAnnotation.getTracksList()) { + VideoSegment segment = track.getSegment(); + System.out.printf( + "\tStart: %d.%.0fs\n", + segment.getStartTimeOffset().getSeconds(), + segment.getStartTimeOffset().getNanos() / 1e6); + System.out.printf( + "\tEnd: %d.%.0fs\n", + segment.getEndTimeOffset().getSeconds(), segment.getEndTimeOffset().getNanos() / 1e6); + + // Each segment includes timestamped objects that include characteristic--e.g. clothes, + // posture of the person detected. + TimestampedObject firstTimestampedObject = track.getTimestampedObjects(0); + + // Attributes include unique pieces of clothing, poses (i.e., body landmarks) + // of the person detected. + for (DetectedAttribute attribute : firstTimestampedObject.getAttributesList()) { + System.out.printf( + "\tAttribute: %s; Value: %s\n", attribute.getName(), attribute.getValue()); + } + + // Landmarks in person detection include body parts. + for (DetectedLandmark attribute : firstTimestampedObject.getLandmarksList()) { + System.out.printf( + "\tLandmark: %s; Vertex: %f, %f\n", + attribute.getName(), attribute.getPoint().getX(), attribute.getPoint().getY()); + } + } + } + } + } +} +// [END video_detect_person_gcs] diff --git a/video/src/main/java/video/LogoDetection.java b/video/src/main/java/video/LogoDetection.java new file mode 100644 index 00000000000..4a110e4a351 --- /dev/null +++ b/video/src/main/java/video/LogoDetection.java @@ -0,0 +1,141 @@ +/* + * 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 video; + +// [START video_detect_logo] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.videointelligence.v1.AnnotateVideoProgress; +import com.google.cloud.videointelligence.v1.AnnotateVideoRequest; +import com.google.cloud.videointelligence.v1.AnnotateVideoResponse; +import com.google.cloud.videointelligence.v1.DetectedAttribute; +import com.google.cloud.videointelligence.v1.Entity; +import com.google.cloud.videointelligence.v1.Feature; +import com.google.cloud.videointelligence.v1.LogoRecognitionAnnotation; +import com.google.cloud.videointelligence.v1.NormalizedBoundingBox; +import com.google.cloud.videointelligence.v1.TimestampedObject; +import com.google.cloud.videointelligence.v1.Track; +import com.google.cloud.videointelligence.v1.VideoAnnotationResults; +import com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient; +import com.google.cloud.videointelligence.v1.VideoSegment; +import com.google.protobuf.ByteString; +import com.google.protobuf.Duration; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class LogoDetection { + + public static void detectLogo() throws Exception { + // TODO(developer): Replace these variables before running the sample. + String localFilePath = "path/to/your/video.mp4"; + detectLogo(localFilePath); + } + + public static void detectLogo(String filePath) + 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 (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Read file + Path path = Paths.get(filePath); + byte[] data = Files.readAllBytes(path); + // Create the request + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputContent(ByteString.copyFrom(data)) + .addFeatures(Feature.LOGO_RECOGNITION) + .build(); + + // asynchronously perform object tracking on videos + OperationFuture future = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + // The first result is retrieved because a single video was processed. + AnnotateVideoResponse response = future.get(300, TimeUnit.SECONDS); + VideoAnnotationResults annotationResult = response.getAnnotationResults(0); + + // Annotations for list of logos detected, tracked and recognized in video. + for (LogoRecognitionAnnotation logoRecognitionAnnotation : + annotationResult.getLogoRecognitionAnnotationsList()) { + Entity entity = logoRecognitionAnnotation.getEntity(); + // Opaque entity ID. Some IDs may be available in + // [Google Knowledge Graph Search API](https://developers.google.com/knowledge-graph/). + System.out.printf("Entity Id : %s\n", entity.getEntityId()); + System.out.printf("Description : %s\n", entity.getDescription()); + // All logo tracks where the recognized logo appears. Each track corresponds to one logo + // instance appearing in consecutive frames. + for (Track track : logoRecognitionAnnotation.getTracksList()) { + + // Video segment of a track. + Duration startTimeOffset = track.getSegment().getStartTimeOffset(); + System.out.printf( + "\n\tStart Time Offset: %s.%s\n", + startTimeOffset.getSeconds(), startTimeOffset.getNanos()); + Duration endTimeOffset = track.getSegment().getEndTimeOffset(); + System.out.printf( + "\tEnd Time Offset: %s.%s\n", endTimeOffset.getSeconds(), endTimeOffset.getNanos()); + System.out.printf("\tConfidence: %s\n", track.getConfidence()); + + // The object with timestamp and attributes per frame in the track. + for (TimestampedObject timestampedObject : track.getTimestampedObjectsList()) { + + // Normalized Bounding box in a frame, where the object is located. + NormalizedBoundingBox normalizedBoundingBox = + timestampedObject.getNormalizedBoundingBox(); + System.out.printf("\n\t\tLeft: %s\n", normalizedBoundingBox.getLeft()); + System.out.printf("\t\tTop: %s\n", normalizedBoundingBox.getTop()); + System.out.printf("\t\tRight: %s\n", normalizedBoundingBox.getRight()); + System.out.printf("\t\tBottom: %s\n", normalizedBoundingBox.getBottom()); + + // Optional. The attributes of the object in the bounding box. + for (DetectedAttribute attribute : timestampedObject.getAttributesList()) { + System.out.printf("\n\t\t\tName: %s\n", attribute.getName()); + System.out.printf("\t\t\tConfidence: %s\n", attribute.getConfidence()); + System.out.printf("\t\t\tValue: %s\n", attribute.getValue()); + } + } + + // Optional. Attributes in the track level. + for (DetectedAttribute trackAttribute : track.getAttributesList()) { + System.out.printf("\n\t\tName : %s\n", trackAttribute.getName()); + System.out.printf("\t\tConfidence : %s\n", trackAttribute.getConfidence()); + System.out.printf("\t\tValue : %s\n", trackAttribute.getValue()); + } + } + + // All video segments where the recognized logo appears. There might be multiple instances + // of the same logo class appearing in one VideoSegment. + for (VideoSegment segment : logoRecognitionAnnotation.getSegmentsList()) { + System.out.printf( + "\n\tStart Time Offset : %s.%s\n", + segment.getStartTimeOffset().getSeconds(), segment.getStartTimeOffset().getNanos()); + System.out.printf( + "\tEnd Time Offset : %s.%s\n", + segment.getEndTimeOffset().getSeconds(), segment.getEndTimeOffset().getNanos()); + } + } + } + } +} +// [END video_detect_logo] diff --git a/video/src/main/java/video/LogoDetectionGcs.java b/video/src/main/java/video/LogoDetectionGcs.java new file mode 100644 index 00000000000..714c308c58a --- /dev/null +++ b/video/src/main/java/video/LogoDetectionGcs.java @@ -0,0 +1,134 @@ +/* + * 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 video; + +// [START video_detect_logo_gcs] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.videointelligence.v1.AnnotateVideoProgress; +import com.google.cloud.videointelligence.v1.AnnotateVideoRequest; +import com.google.cloud.videointelligence.v1.AnnotateVideoResponse; +import com.google.cloud.videointelligence.v1.DetectedAttribute; +import com.google.cloud.videointelligence.v1.Entity; +import com.google.cloud.videointelligence.v1.Feature; +import com.google.cloud.videointelligence.v1.LogoRecognitionAnnotation; +import com.google.cloud.videointelligence.v1.NormalizedBoundingBox; +import com.google.cloud.videointelligence.v1.TimestampedObject; +import com.google.cloud.videointelligence.v1.Track; +import com.google.cloud.videointelligence.v1.VideoAnnotationResults; +import com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient; +import com.google.cloud.videointelligence.v1.VideoSegment; +import com.google.protobuf.Duration; +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class LogoDetectionGcs { + + public static void detectLogoGcs() throws Exception { + // TODO(developer): Replace these variables before running the sample. + String gcsUri = "gs://YOUR_BUCKET_ID/path/to/your/video.mp4"; + detectLogoGcs(gcsUri); + } + + public static void detectLogoGcs(String inputUri) + 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 (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Create the request + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputUri(inputUri) + .addFeatures(Feature.LOGO_RECOGNITION) + .build(); + + // asynchronously perform object tracking on videos + OperationFuture future = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + // The first result is retrieved because a single video was processed. + AnnotateVideoResponse response = future.get(300, TimeUnit.SECONDS); + VideoAnnotationResults annotationResult = response.getAnnotationResults(0); + + // Annotations for list of logos detected, tracked and recognized in video. + for (LogoRecognitionAnnotation logoRecognitionAnnotation : + annotationResult.getLogoRecognitionAnnotationsList()) { + Entity entity = logoRecognitionAnnotation.getEntity(); + // Opaque entity ID. Some IDs may be available in + // [Google Knowledge Graph Search API](https://developers.google.com/knowledge-graph/). + System.out.printf("Entity Id : %s\n", entity.getEntityId()); + System.out.printf("Description : %s\n", entity.getDescription()); + // All logo tracks where the recognized logo appears. Each track corresponds to one logo + // instance appearing in consecutive frames. + for (Track track : logoRecognitionAnnotation.getTracksList()) { + + // Video segment of a track. + Duration startTimeOffset = track.getSegment().getStartTimeOffset(); + System.out.printf( + "\n\tStart Time Offset: %s.%s\n", + startTimeOffset.getSeconds(), startTimeOffset.getNanos()); + Duration endTimeOffset = track.getSegment().getEndTimeOffset(); + System.out.printf( + "\tEnd Time Offset: %s.%s\n", endTimeOffset.getSeconds(), endTimeOffset.getNanos()); + System.out.printf("\tConfidence: %s\n", track.getConfidence()); + + // The object with timestamp and attributes per frame in the track. + for (TimestampedObject timestampedObject : track.getTimestampedObjectsList()) { + + // Normalized Bounding box in a frame, where the object is located. + NormalizedBoundingBox normalizedBoundingBox = + timestampedObject.getNormalizedBoundingBox(); + System.out.printf("\n\t\tLeft: %s\n", normalizedBoundingBox.getLeft()); + System.out.printf("\t\tTop: %s\n", normalizedBoundingBox.getTop()); + System.out.printf("\t\tRight: %s\n", normalizedBoundingBox.getRight()); + System.out.printf("\t\tBottom: %s\n", normalizedBoundingBox.getBottom()); + + // Optional. The attributes of the object in the bounding box. + for (DetectedAttribute attribute : timestampedObject.getAttributesList()) { + System.out.printf("\n\t\t\tName: %s\n", attribute.getName()); + System.out.printf("\t\t\tConfidence: %s\n", attribute.getConfidence()); + System.out.printf("\t\t\tValue: %s\n", attribute.getValue()); + } + } + + // Optional. Attributes in the track level. + for (DetectedAttribute trackAttribute : track.getAttributesList()) { + System.out.printf("\n\t\tName : %s\n", trackAttribute.getName()); + System.out.printf("\t\tConfidence : %s\n", trackAttribute.getConfidence()); + System.out.printf("\t\tValue : %s\n", trackAttribute.getValue()); + } + } + + // All video segments where the recognized logo appears. There might be multiple instances + // of the same logo class appearing in one VideoSegment. + for (VideoSegment segment : logoRecognitionAnnotation.getSegmentsList()) { + System.out.printf( + "\n\tStart Time Offset : %s.%s\n", + segment.getStartTimeOffset().getSeconds(), segment.getStartTimeOffset().getNanos()); + System.out.printf( + "\tEnd Time Offset : %s.%s\n", + segment.getEndTimeOffset().getSeconds(), segment.getEndTimeOffset().getNanos()); + } + } + } + } +} +// [END video_detect_logo_gcs] diff --git a/video/src/main/java/video/QuickstartSample.java b/video/src/main/java/video/QuickstartSample.java new file mode 100644 index 00000000000..9f0bd88f67e --- /dev/null +++ b/video/src/main/java/video/QuickstartSample.java @@ -0,0 +1,85 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package video; + +// [START video_quickstart] + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.videointelligence.v1.AnnotateVideoProgress; +import com.google.cloud.videointelligence.v1.AnnotateVideoRequest; +import com.google.cloud.videointelligence.v1.AnnotateVideoResponse; +import com.google.cloud.videointelligence.v1.Entity; +import com.google.cloud.videointelligence.v1.Feature; +import com.google.cloud.videointelligence.v1.LabelAnnotation; +import com.google.cloud.videointelligence.v1.LabelSegment; +import com.google.cloud.videointelligence.v1.VideoAnnotationResults; +import com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient; +import java.util.List; + +public class QuickstartSample { + + /** Demonstrates using the video intelligence client to detect labels in a video file. */ + public static void main(String[] args) throws Exception { + // Instantiate a video intelligence client + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // The Google Cloud Storage path to the video to annotate. + String gcsUri = "gs://cloud-samples-data/video/cat.mp4"; + + // Create an operation that will contain the response when the operation completes. + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputUri(gcsUri) + .addFeatures(Feature.LABEL_DETECTION) + .build(); + + OperationFuture response = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + + List results = response.get().getAnnotationResultsList(); + if (results.isEmpty()) { + System.out.println("No labels detected in " + gcsUri); + return; + } + for (VideoAnnotationResults result : results) { + System.out.println("Labels:"); + // get video segment label annotations + for (LabelAnnotation annotation : result.getSegmentLabelAnnotationsList()) { + System.out.println( + "Video label description : " + annotation.getEntity().getDescription()); + // categories + for (Entity categoryEntity : annotation.getCategoryEntitiesList()) { + System.out.println("Label Category description : " + categoryEntity.getDescription()); + } + // segments + for (LabelSegment segment : annotation.getSegmentsList()) { + double startTime = + segment.getSegment().getStartTimeOffset().getSeconds() + + segment.getSegment().getStartTimeOffset().getNanos() / 1e9; + double endTime = + segment.getSegment().getEndTimeOffset().getSeconds() + + segment.getSegment().getEndTimeOffset().getNanos() / 1e9; + System.out.printf("Segment location : %.3f:%.3f\n", startTime, endTime); + System.out.println("Confidence : " + segment.getConfidence()); + } + } + } + } + } +} +// [END video_quickstart] diff --git a/video/src/main/java/video/TextDetection.java b/video/src/main/java/video/TextDetection.java new file mode 100644 index 00000000000..aa8d1d929ba --- /dev/null +++ b/video/src/main/java/video/TextDetection.java @@ -0,0 +1,174 @@ +/* + * 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 video; + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.videointelligence.v1.AnnotateVideoProgress; +import com.google.cloud.videointelligence.v1.AnnotateVideoRequest; +import com.google.cloud.videointelligence.v1.AnnotateVideoResponse; +import com.google.cloud.videointelligence.v1.Feature; +import com.google.cloud.videointelligence.v1.NormalizedVertex; +import com.google.cloud.videointelligence.v1.TextAnnotation; +import com.google.cloud.videointelligence.v1.TextFrame; +import com.google.cloud.videointelligence.v1.TextSegment; +import com.google.cloud.videointelligence.v1.VideoAnnotationResults; +import com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient; +import com.google.cloud.videointelligence.v1.VideoSegment; +import com.google.protobuf.ByteString; +import com.google.protobuf.Duration; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class TextDetection { + + // [START video_detect_text] + /** + * Detect text in a video. + * + * @param filePath the path to the video file to analyze. + */ + public static VideoAnnotationResults detectText(String filePath) throws Exception { + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Read file + Path path = Paths.get(filePath); + byte[] data = Files.readAllBytes(path); + + // Create the request + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputContent(ByteString.copyFrom(data)) + .addFeatures(Feature.TEXT_DETECTION) + .build(); + + // asynchronously perform object tracking on videos + OperationFuture future = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + // The first result is retrieved because a single video was processed. + AnnotateVideoResponse response = future.get(300, TimeUnit.SECONDS); + VideoAnnotationResults results = response.getAnnotationResults(0); + + // Get only the first annotation for demo purposes. + TextAnnotation annotation = results.getTextAnnotations(0); + System.out.println("Text: " + annotation.getText()); + + // Get the first text segment. + TextSegment textSegment = annotation.getSegments(0); + System.out.println("Confidence: " + textSegment.getConfidence()); + // For the text segment display it's time offset + VideoSegment videoSegment = textSegment.getSegment(); + Duration startTimeOffset = videoSegment.getStartTimeOffset(); + Duration endTimeOffset = videoSegment.getEndTimeOffset(); + // Display the offset times in seconds, 1e9 is part of the formula to convert nanos to seconds + System.out.println( + String.format( + "Start time: %.2f", startTimeOffset.getSeconds() + startTimeOffset.getNanos() / 1e9)); + System.out.println( + String.format( + "End time: %.2f", endTimeOffset.getSeconds() + endTimeOffset.getNanos() / 1e9)); + + // Show the first result for the first frame in the segment. + TextFrame textFrame = textSegment.getFrames(0); + Duration timeOffset = textFrame.getTimeOffset(); + System.out.println( + String.format( + "Time offset for the first frame: %.2f", + timeOffset.getSeconds() + timeOffset.getNanos() / 1e9)); + + // Display the rotated bounding box for where the text is on the frame. + System.out.println("Rotated Bounding Box Vertices:"); + List vertices = textFrame.getRotatedBoundingBox().getVerticesList(); + for (NormalizedVertex normalizedVertex : vertices) { + System.out.println( + String.format( + "\tVertex.x: %.2f, Vertex.y: %.2f", + normalizedVertex.getX(), normalizedVertex.getY())); + } + return results; + } + } + // [END video_detect_text] + + // [START video_detect_text_gcs] + /** + * Detect Text in a video. + * + * @param gcsUri the path to the video file to analyze. + */ + public static VideoAnnotationResults detectTextGcs(String gcsUri) throws Exception { + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Create the request + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputUri(gcsUri) + .addFeatures(Feature.TEXT_DETECTION) + .build(); + + // asynchronously perform object tracking on videos + OperationFuture future = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + // The first result is retrieved because a single video was processed. + AnnotateVideoResponse response = future.get(300, TimeUnit.SECONDS); + VideoAnnotationResults results = response.getAnnotationResults(0); + + // Get only the first annotation for demo purposes. + TextAnnotation annotation = results.getTextAnnotations(0); + System.out.println("Text: " + annotation.getText()); + + // Get the first text segment. + TextSegment textSegment = annotation.getSegments(0); + System.out.println("Confidence: " + textSegment.getConfidence()); + // For the text segment display it's time offset + VideoSegment videoSegment = textSegment.getSegment(); + Duration startTimeOffset = videoSegment.getStartTimeOffset(); + Duration endTimeOffset = videoSegment.getEndTimeOffset(); + // Display the offset times in seconds, 1e9 is part of the formula to convert nanos to seconds + System.out.println( + String.format( + "Start time: %.2f", startTimeOffset.getSeconds() + startTimeOffset.getNanos() / 1e9)); + System.out.println( + String.format( + "End time: %.2f", endTimeOffset.getSeconds() + endTimeOffset.getNanos() / 1e9)); + + // Show the first result for the first frame in the segment. + TextFrame textFrame = textSegment.getFrames(0); + Duration timeOffset = textFrame.getTimeOffset(); + System.out.println( + String.format( + "Time offset for the first frame: %.2f", + timeOffset.getSeconds() + timeOffset.getNanos() / 1e9)); + + // Display the rotated bounding box for where the text is on the frame. + System.out.println("Rotated Bounding Box Vertices:"); + List vertices = textFrame.getRotatedBoundingBox().getVerticesList(); + for (NormalizedVertex normalizedVertex : vertices) { + System.out.println( + String.format( + "\tVertex.x: %.2f, Vertex.y: %.2f", + normalizedVertex.getX(), normalizedVertex.getY())); + } + return results; + } + } + // [END video_detect_text_gcs] +} diff --git a/video/src/main/java/video/TrackObjects.java b/video/src/main/java/video/TrackObjects.java new file mode 100644 index 00000000000..211948eeb75 --- /dev/null +++ b/video/src/main/java/video/TrackObjects.java @@ -0,0 +1,179 @@ +/* + * 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 video; + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.videointelligence.v1.AnnotateVideoProgress; +import com.google.cloud.videointelligence.v1.AnnotateVideoRequest; +import com.google.cloud.videointelligence.v1.AnnotateVideoResponse; +import com.google.cloud.videointelligence.v1.Entity; +import com.google.cloud.videointelligence.v1.Feature; +import com.google.cloud.videointelligence.v1.NormalizedBoundingBox; +import com.google.cloud.videointelligence.v1.ObjectTrackingAnnotation; +import com.google.cloud.videointelligence.v1.ObjectTrackingFrame; +import com.google.cloud.videointelligence.v1.VideoAnnotationResults; +import com.google.cloud.videointelligence.v1.VideoIntelligenceServiceClient; +import com.google.cloud.videointelligence.v1.VideoSegment; +import com.google.protobuf.ByteString; +import com.google.protobuf.Duration; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.TimeUnit; + +public class TrackObjects { + + // [START video_object_tracking] + /** + * Track objects in a video. + * + * @param filePath the path to the video file to analyze. + */ + public static VideoAnnotationResults trackObjects(String filePath) throws Exception { + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Read file + Path path = Paths.get(filePath); + byte[] data = Files.readAllBytes(path); + + // Create the request + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputContent(ByteString.copyFrom(data)) + .addFeatures(Feature.OBJECT_TRACKING) + .setLocationId("us-east1") + .build(); + + // asynchronously perform object tracking on videos + OperationFuture future = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + // The first result is retrieved because a single video was processed. + AnnotateVideoResponse response = future.get(450, TimeUnit.SECONDS); + VideoAnnotationResults results = response.getAnnotationResults(0); + + // Get only the first annotation for demo purposes. + ObjectTrackingAnnotation annotation = results.getObjectAnnotations(0); + System.out.println("Confidence: " + annotation.getConfidence()); + + if (annotation.hasEntity()) { + Entity entity = annotation.getEntity(); + System.out.println("Entity description: " + entity.getDescription()); + System.out.println("Entity id:: " + entity.getEntityId()); + } + + if (annotation.hasSegment()) { + VideoSegment videoSegment = annotation.getSegment(); + Duration startTimeOffset = videoSegment.getStartTimeOffset(); + Duration endTimeOffset = videoSegment.getEndTimeOffset(); + // Display the segment time in seconds, 1e9 converts nanos to seconds + System.out.println( + String.format( + "Segment: %.2fs to %.2fs", + startTimeOffset.getSeconds() + startTimeOffset.getNanos() / 1e9, + endTimeOffset.getSeconds() + endTimeOffset.getNanos() / 1e9)); + } + + // Here we print only the bounding box of the first frame in this segment. + ObjectTrackingFrame frame = annotation.getFrames(0); + // Display the offset time in seconds, 1e9 converts nanos to seconds + Duration timeOffset = frame.getTimeOffset(); + System.out.println( + String.format( + "Time offset of the first frame: %.2fs", + timeOffset.getSeconds() + timeOffset.getNanos() / 1e9)); + + // Display the bounding box of the detected object + NormalizedBoundingBox normalizedBoundingBox = frame.getNormalizedBoundingBox(); + System.out.println("Bounding box position:"); + System.out.println("\tleft: " + normalizedBoundingBox.getLeft()); + System.out.println("\ttop: " + normalizedBoundingBox.getTop()); + System.out.println("\tright: " + normalizedBoundingBox.getRight()); + System.out.println("\tbottom: " + normalizedBoundingBox.getBottom()); + return results; + } + } + // [END video_object_tracking] + + // [START video_object_tracking_gcs] + /** + * Track objects in a video. + * + * @param gcsUri the path to the video file to analyze. + */ + public static VideoAnnotationResults trackObjectsGcs(String gcsUri) throws Exception { + try (VideoIntelligenceServiceClient client = VideoIntelligenceServiceClient.create()) { + // Create the request + AnnotateVideoRequest request = + AnnotateVideoRequest.newBuilder() + .setInputUri(gcsUri) + .addFeatures(Feature.OBJECT_TRACKING) + .setLocationId("us-east1") + .build(); + + // asynchronously perform object tracking on videos + OperationFuture future = + client.annotateVideoAsync(request); + + System.out.println("Waiting for operation to complete..."); + // The first result is retrieved because a single video was processed. + AnnotateVideoResponse response = future.get(450, TimeUnit.SECONDS); + VideoAnnotationResults results = response.getAnnotationResults(0); + + // Get only the first annotation for demo purposes. + ObjectTrackingAnnotation annotation = results.getObjectAnnotations(0); + System.out.println("Confidence: " + annotation.getConfidence()); + + if (annotation.hasEntity()) { + Entity entity = annotation.getEntity(); + System.out.println("Entity description: " + entity.getDescription()); + System.out.println("Entity id:: " + entity.getEntityId()); + } + + if (annotation.hasSegment()) { + VideoSegment videoSegment = annotation.getSegment(); + Duration startTimeOffset = videoSegment.getStartTimeOffset(); + Duration endTimeOffset = videoSegment.getEndTimeOffset(); + // Display the segment time in seconds, 1e9 converts nanos to seconds + System.out.println( + String.format( + "Segment: %.2fs to %.2fs", + startTimeOffset.getSeconds() + startTimeOffset.getNanos() / 1e9, + endTimeOffset.getSeconds() + endTimeOffset.getNanos() / 1e9)); + } + + // Here we print only the bounding box of the first frame in this segment. + ObjectTrackingFrame frame = annotation.getFrames(0); + // Display the offset time in seconds, 1e9 converts nanos to seconds + Duration timeOffset = frame.getTimeOffset(); + System.out.println( + String.format( + "Time offset of the first frame: %.2fs", + timeOffset.getSeconds() + timeOffset.getNanos() / 1e9)); + + // Display the bounding box of the detected object + NormalizedBoundingBox normalizedBoundingBox = frame.getNormalizedBoundingBox(); + System.out.println("Bounding box position:"); + System.out.println("\tleft: " + normalizedBoundingBox.getLeft()); + System.out.println("\ttop: " + normalizedBoundingBox.getTop()); + System.out.println("\tright: " + normalizedBoundingBox.getRight()); + System.out.println("\tbottom: " + normalizedBoundingBox.getBottom()); + return results; + } + } + // [END video_object_tracking_gcs] +} diff --git a/video/src/test/java/beta/video/DetectIT.java b/video/src/test/java/beta/video/DetectIT.java new file mode 100644 index 00000000000..11f97a342f2 --- /dev/null +++ b/video/src/test/java/beta/video/DetectIT.java @@ -0,0 +1,150 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.video; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.testing.junit4.MultipleAttemptsRule; +import com.google.cloud.videointelligence.v1p2beta1.ObjectTrackingAnnotation; +import com.google.cloud.videointelligence.v1p2beta1.TextAnnotation; +import com.google.cloud.videointelligence.v1p2beta1.VideoAnnotationResults; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeoutException; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for video analysis sample. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class DetectIT { + + static final String FILE_LOCATION = "gs://java-docs-samples-testing/video/googlework_short.mp4"; + private static final List POSSIBLE_TEXTS = + Arrays.asList( + "Google", + "SUR", + "SUR", + "ROTO", + "Vice President", + "58oo9", + "LONDRES", + "OMAR", + "PARIS", + "METRO", + "RUE", + "CARLO"); + private ByteArrayOutputStream bout; + private PrintStream out; + private PrintStream originalPrintStream; + + @Rule public MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); + + @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); + } + + @Test + public void testSpeechTranscription() throws Exception { + String[] args = {"speech-transcription", FILE_LOCATION}; + Detect.argsHelper(args); + String got = bout.toString(); + + assertThat(got).contains("cultural"); + } + + @Test + public void testTrackObjects() throws Exception { + TrackObjects.trackObjects("resources/googlework_short.mp4"); + + String got = bout.toString(); + + assertThat(got).contains("Entity id"); + } + + @Test + public void testTrackObjectsGcs() throws Exception { + VideoAnnotationResults result = + TrackObjects.trackObjectsGcs("gs://cloud-samples-data/video/cat.mp4"); + + boolean textExists = false; + for (ObjectTrackingAnnotation objectTrackingAnnotation : result.getObjectAnnotationsList()) { + if (objectTrackingAnnotation.getEntity().getDescription().toUpperCase().contains("CAT")) { + textExists = true; + break; + } + } + + assertThat(textExists).isTrue(); + } + + @Test + public void testTextDetection() throws Exception { + try { + VideoAnnotationResults result = TextDetection.detectText("resources/googlework_short.mp4"); + boolean textExists = false; + for (TextAnnotation textAnnotation : result.getTextAnnotationsList()) { + for (String possibleText : POSSIBLE_TEXTS) { + if (textAnnotation.getText().toUpperCase().contains(possibleText.toUpperCase())) { + textExists = true; + break; + } + } + } + + assertThat(textExists).isTrue(); + + } catch (TimeoutException ex) { + Assert.assertTrue(ex.getMessage().contains("Waited")); + } + } + + @Test + public void testTextDetectionGcs() throws Exception { + VideoAnnotationResults result = TextDetection.detectTextGcs(FILE_LOCATION); + + boolean textExists = false; + for (TextAnnotation textAnnotation : result.getTextAnnotationsList()) { + for (String possibleText : POSSIBLE_TEXTS) { + if (textAnnotation.getText().toUpperCase().contains(possibleText.toUpperCase())) { + textExists = true; + break; + } + } + } + + assertThat(textExists).isTrue(); + } +} diff --git a/video/src/test/java/beta/video/DetectLogoGcsTest.java b/video/src/test/java/beta/video/DetectLogoGcsTest.java new file mode 100644 index 00000000000..3e80e4695ee --- /dev/null +++ b/video/src/test/java/beta/video/DetectLogoGcsTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.video; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class DetectLogoGcsTest { + + 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); + } + + @Test + public void testDetectFaces() throws Exception { + DetectLogoGcs.detectLogoGcs("gs://cloud-samples-data/video/googlework_short.mp4"); + String got = bout.toString(); + assertThat(got).contains("Entity Id"); + } +} diff --git a/video/src/test/java/beta/video/DetectLogoTest.java b/video/src/test/java/beta/video/DetectLogoTest.java new file mode 100644 index 00000000000..64a56092a2d --- /dev/null +++ b/video/src/test/java/beta/video/DetectLogoTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.video; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class DetectLogoTest { + + 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); + } + + @Test + public void testDetectFaces() throws Exception { + DetectLogo.detectLogo("resources/googlework_short.mp4"); + String got = bout.toString(); + assertThat(got).contains("Entity Id"); + } +} diff --git a/video/src/test/java/beta/video/StreamingAnnotationToStorageIT.java b/video/src/test/java/beta/video/StreamingAnnotationToStorageIT.java new file mode 100644 index 00000000000..9b0f9ef1098 --- /dev/null +++ b/video/src/test/java/beta/video/StreamingAnnotationToStorageIT.java @@ -0,0 +1,95 @@ +/* + * 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.video; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.api.gax.paging.Page; +import com.google.cloud.storage.Blob; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.Storage.BlobListOption; +import com.google.cloud.storage.StorageOptions; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.concurrent.TimeoutException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Integration (system) tests for {@link StreamingAnnotationToStorage}. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class StreamingAnnotationToStorageIT { + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String OUTPUT_PREFIX = "VIDEO_STREAMING_TEST_OUTPUT"; + + 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); + } + + @Test + public void testStreamingAnnotationToStorage() throws IOException, TimeoutException { + String gcsUri = String.format("gs://%s/%s", PROJECT_ID, OUTPUT_PREFIX); + StreamingAnnotationToStorage.streamingAnnotationToStorage("resources/cat.mp4", gcsUri); + String got = bout.toString(); + + assertThat(got).contains(String.format("Storage Uri: %s", gcsUri)); + + Storage storage = StorageOptions.getDefaultInstance().getService(); + + Page blobs = + storage.list( + PROJECT_ID, + BlobListOption.currentDirectory(), + BlobListOption.prefix(OUTPUT_PREFIX + "/")); + + deleteDirectory(storage, blobs); + } + + private void deleteDirectory(Storage storage, Page blobs) { + for (Blob blob : blobs.iterateAll()) { + System.out.println(blob.getName()); + if (!blob.delete()) { + Page subBlobs = + storage.list( + PROJECT_ID, + BlobListOption.currentDirectory(), + BlobListOption.prefix(blob.getName())); + + deleteDirectory(storage, subBlobs); + } + } + } +} diff --git a/video/src/test/java/beta/video/StreamingAutoMlActionRecognitionIT.java b/video/src/test/java/beta/video/StreamingAutoMlActionRecognitionIT.java new file mode 100644 index 00000000000..b0fd040a5b3 --- /dev/null +++ b/video/src/test/java/beta/video/StreamingAutoMlActionRecognitionIT.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 beta.video; + +import static com.google.common.truth.Truth.assertThat; + +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Integration (system) tests for {@link StreamingAutoMlActionRecognition}. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class StreamingAutoMlActionRecognitionIT { + + private static String PROJECT_ID = System.getenv().get("GOOGLE_CLOUD_PROJECT"); + private static String MODEL_ID = "2787930479481847808"; + + 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); + } + + @Test + public void testStreamingAutoMlActionRecognition() { + // Bad Gateway sporadically occurs + int tryCount = 0; + int maxTries = 3; + while (tryCount < maxTries) { + try { + StreamingAutoMlActionRecognition.streamingAutoMlActionRecognition( + "resources/cat.mp4", PROJECT_ID, MODEL_ID); + assertThat(bout.toString()).contains("Video streamed successfully."); + + break; + } catch (StatusRuntimeException ex) { + if (ex.getStatus().getCode() == Status.Code.UNAVAILABLE) { + assertThat(ex.getMessage()).contains("Bad Gateway"); + tryCount++; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } +} diff --git a/video/src/test/java/beta/video/StreamingAutoMlClassificationIT.java b/video/src/test/java/beta/video/StreamingAutoMlClassificationIT.java new file mode 100644 index 00000000000..e412833ae5d --- /dev/null +++ b/video/src/test/java/beta/video/StreamingAutoMlClassificationIT.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 beta.video; + +import static com.google.common.truth.Truth.assertThat; + +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Integration (system) tests for {@link StreamingAutoMlClassification}. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class StreamingAutoMlClassificationIT { + + private static String PROJECT_ID = "779844219229"; // System.getenv().get("GOOGLE_CLOUD_PROJECT"); + private static String MODEL_ID = "VCN6455760532254228480"; + + 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); + } + + @Test + public void testStreamingAutoMlClassification() { + // Bad Gateway sporadically occurs + int tryCount = 0; + int maxTries = 3; + while (tryCount < maxTries) { + try { + StreamingAutoMlClassification.streamingAutoMlClassification( + "resources/cat.mp4", PROJECT_ID, MODEL_ID); + assertThat(bout.toString()).contains("Video streamed successfully."); + + break; + } catch (StatusRuntimeException ex) { + if (ex.getStatus().getCode() == Status.Code.UNAVAILABLE) { + assertThat(ex.getMessage()).contains("Bad Gateway"); + tryCount++; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } +} diff --git a/video/src/test/java/beta/video/StreamingAutoMlObjectTrackingIT.java b/video/src/test/java/beta/video/StreamingAutoMlObjectTrackingIT.java new file mode 100644 index 00000000000..2d7f082fe2c --- /dev/null +++ b/video/src/test/java/beta/video/StreamingAutoMlObjectTrackingIT.java @@ -0,0 +1,80 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.video; + +import static com.google.common.truth.Truth.assertThat; + +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Integration (system) tests for {@link StreamingAutoMlObjectTracking}. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class StreamingAutoMlObjectTrackingIT { + + private static String PROJECT_ID = System.getenv().get("GOOGLE_CLOUD_PROJECT"); + private static String MODEL_ID = System.getenv().get("VIDEO_OBJECT_TRACKING_MODEL_ID"); + + 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); + } + + @Test + public void testStreamingAutoMlObjectTracking() { + // Bad Gateway sporadically occurs + int tryCount = 0; + int maxTries = 3; + while (tryCount < maxTries) { + try { + StreamingAutoMlObjectTracking.streamingAutoMlObjectTracking( + "resources/cat.mp4", PROJECT_ID, MODEL_ID); + assertThat(bout.toString()).contains("Video streamed successfully."); + + break; + } catch (StatusRuntimeException ex) { + if (ex.getStatus().getCode() == Status.Code.UNAVAILABLE) { + assertThat(ex.getMessage()).contains("Bad Gateway"); + tryCount++; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } +} diff --git a/video/src/test/java/beta/video/StreamingExplicitContentDetectionIT.java b/video/src/test/java/beta/video/StreamingExplicitContentDetectionIT.java new file mode 100644 index 00000000000..6ba53cba575 --- /dev/null +++ b/video/src/test/java/beta/video/StreamingExplicitContentDetectionIT.java @@ -0,0 +1,61 @@ +/* + * 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.video; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.concurrent.TimeoutException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Integration (system) tests for {@link StreamingExplicitContentDetection}. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class StreamingExplicitContentDetectionIT { + 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); + } + + @Test + public void testStreamingExplicitContent() throws IOException, TimeoutException { + StreamingExplicitContentDetection.streamingExplicitContentDetection("resources/cat.mp4"); + String got = bout.toString(); + + assertThat(got).contains("UNLIKELY"); + } +} diff --git a/video/src/test/java/beta/video/StreamingLabelDetectionIT.java b/video/src/test/java/beta/video/StreamingLabelDetectionIT.java new file mode 100644 index 00000000000..e779725b9d9 --- /dev/null +++ b/video/src/test/java/beta/video/StreamingLabelDetectionIT.java @@ -0,0 +1,61 @@ +/* + * 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.video; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.concurrent.TimeoutException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Integration (system) tests for {@link StreamingLabelDetection}. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class StreamingLabelDetectionIT { + 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); + } + + @Test + public void testStreamingLabelDetection() throws IOException, TimeoutException { + StreamingLabelDetection.streamingLabelDetection("resources/cat.mp4"); + String got = bout.toString(); + + assertThat(got).contains("cat"); + } +} diff --git a/video/src/test/java/beta/video/StreamingObjectTrackingIT.java b/video/src/test/java/beta/video/StreamingObjectTrackingIT.java new file mode 100644 index 00000000000..2f16c1bd6d7 --- /dev/null +++ b/video/src/test/java/beta/video/StreamingObjectTrackingIT.java @@ -0,0 +1,65 @@ +/* + * 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.video; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.concurrent.TimeoutException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Integration (system) tests for {@link StreamingObjectTracking}. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class StreamingObjectTrackingIT { + 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); + } + + @Test + public void testStreamingObjectTracking() throws IOException, TimeoutException { + StreamingObjectTracking.streamingObjectTracking("resources/cat.mp4"); + String got = bout.toString(); + + assertThat(got).contains("cat"); + assertThat(got).contains("Left: 0.1"); + assertThat(got).contains("Top: 0.2"); + assertThat(got).contains("Right: 0.7"); + assertThat(got).contains("Bottom: 0.8"); + } +} diff --git a/video/src/test/java/beta/video/StreamingShotChangeDetectionIT.java b/video/src/test/java/beta/video/StreamingShotChangeDetectionIT.java new file mode 100644 index 00000000000..6387cba1c8e --- /dev/null +++ b/video/src/test/java/beta/video/StreamingShotChangeDetectionIT.java @@ -0,0 +1,68 @@ +/* + * 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.video; + +import static com.google.common.truth.Truth.assertThat; + +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.TimeoutException; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Integration (system) tests for {@link StreamingShotChangeDetection}. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class StreamingShotChangeDetectionIT { + @Rule public final MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); + + 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); + } + + @Test + public void testStreamingShotChangeDetection() + throws IOException, TimeoutException, StatusRuntimeException { + StreamingShotChangeDetection.streamingShotChangeDetection("resources/cat.mp4"); + String got = bout.toString(); + + assertThat(got).contains("Shot: 0.0"); + assertThat(got).contains("to 14.8"); + } +} diff --git a/video/src/test/java/com/example/video/DetectIT.java b/video/src/test/java/com/example/video/DetectIT.java new file mode 100644 index 00000000000..d48ad18cd38 --- /dev/null +++ b/video/src/test/java/com/example/video/DetectIT.java @@ -0,0 +1,164 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.video; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.videointelligence.v1.TextAnnotation; +import com.google.cloud.videointelligence.v1.VideoAnnotationResults; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +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; + +/** Tests for video analysis sample. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class DetectIT { + static final String LABEL_GCS_LOCATION = "gs://cloud-samples-data/video/cat.mp4"; + static final String LABEL_FILE_LOCATION = "./resources/googlework_short.mp4"; + static final String SHOTS_FILE_LOCATION = "gs://cloud-samples-data/video/gbikes_dinosaur.mp4"; + static final String EXPLICIT_CONTENT_LOCATION = "gs://cloud-samples-data/video/cat.mp4"; + static final String SPEECH_GCS_LOCATION = + "gs://java-docs-samples-testing/video/googlework_short.mp4"; + private static final List POSSIBLE_TEXTS = + Arrays.asList( + "Google", + "SUR", + "SUR", + "ROTO", + "Vice President", + "58oo9", + "LONDRES", + "OMAR", + "PARIS", + "METRO", + "RUE", + "CARLO"); + 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 testLabels() throws Exception { + String[] args = {"labels", LABEL_GCS_LOCATION}; + Detect.argsHelper(args); + String got = bout.toString(); + assertThat(got).contains("Video label"); + } + + @Test + public void testLabelsFile() throws Exception { + String[] args = {"labels-file", LABEL_FILE_LOCATION}; + Detect.argsHelper(args); + String got = bout.toString(); + assertThat(got).contains("Video label"); + } + + @Test + public void testExplicitContent() throws Exception { + String[] args = {"explicit-content", EXPLICIT_CONTENT_LOCATION}; + Detect.argsHelper(args); + String got = bout.toString(); + assertThat(got).contains("Adult:"); + } + + @Test + public void testShots() throws Exception { + String[] args = {"shots", SHOTS_FILE_LOCATION}; + Detect.argsHelper(args); + String got = bout.toString(); + assertThat(got).contains("Shots:"); + assertThat(got).contains("Location:"); + } + + @Test + public void testSpeechTranscription() throws Exception { + String[] args = {"speech-transcription", SPEECH_GCS_LOCATION}; + Detect.argsHelper(args); + String got = bout.toString(); + + assertThat(got).contains("Transcript"); + } + + @Test + public void testTrackObjects() throws Exception { + TrackObjects.trackObjects("resources/googlework_short.mp4"); + + String got = bout.toString(); + + assertThat(got).contains("Entity id"); + } + + @Test + public void testTrackObjectsGcs() throws Exception { + VideoAnnotationResults result = TrackObjects.trackObjectsGcs(LABEL_GCS_LOCATION); + + String got = bout.toString(); + assertThat(got).contains("Entity id"); + } + + @Test + public void testTextDetection() throws Exception { + VideoAnnotationResults result = TextDetection.detectText("resources/googlework_short.mp4"); + + boolean textExists = false; + for (TextAnnotation textAnnotation : result.getTextAnnotationsList()) { + for (String possibleText : POSSIBLE_TEXTS) { + if (textAnnotation.getText().toUpperCase().contains(possibleText.toUpperCase())) { + textExists = true; + break; + } + } + } + + assertThat(textExists).isTrue(); + } + + @Test + public void testTextDetectionGcs() throws Exception { + VideoAnnotationResults result = TextDetection.detectTextGcs(SPEECH_GCS_LOCATION); + + boolean textExists = false; + for (TextAnnotation textAnnotation : result.getTextAnnotationsList()) { + for (String possibleText : POSSIBLE_TEXTS) { + if (textAnnotation.getText().toUpperCase().contains(possibleText.toUpperCase())) { + textExists = true; + break; + } + } + } + + assertThat(textExists).isTrue(); + } +} diff --git a/video/src/test/java/com/example/video/DetectLogoGcsTest.java b/video/src/test/java/com/example/video/DetectLogoGcsTest.java new file mode 100644 index 00000000000..11715715503 --- /dev/null +++ b/video/src/test/java/com/example/video/DetectLogoGcsTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.video; + +import static com.google.common.truth.Truth.assertThat; + +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.Rule; +import org.junit.Test; + +public class DetectLogoGcsTest { + 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); + } + + @Rule public MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); + + @Test + public void testLogoDetectGcs() + throws IOException, ExecutionException, InterruptedException, TimeoutException { + LogoDetectionGcs.detectLogoGcs("gs://cloud-samples-data/video/googlework_tiny.mp4"); + String got = bout.toString(); + + assertThat(got).contains("Description"); + assertThat(got).contains("Confidence"); + assertThat(got).contains("Start Time Offset"); + assertThat(got).contains("End Time Offset"); + } +} diff --git a/video/src/test/java/com/example/video/DetectLogoTest.java b/video/src/test/java/com/example/video/DetectLogoTest.java new file mode 100644 index 00000000000..cec71886f0d --- /dev/null +++ b/video/src/test/java/com/example/video/DetectLogoTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2020 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.video; + +import static com.google.common.truth.Truth.assertThat; + +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.Test; + +public class DetectLogoTest { + 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 testLogoDetect() + throws IOException, ExecutionException, InterruptedException, TimeoutException { + LogoDetection.detectLogo("resources/googlework_short.mp4"); + String got = bout.toString(); + + assertThat(got).contains("Description"); + assertThat(got).contains("Confidence"); + assertThat(got).contains("Start Time Offset"); + assertThat(got).contains("End Time Offset"); + } +} diff --git a/video/src/test/java/com/example/video/QuickstartIT.java b/video/src/test/java/com/example/video/QuickstartIT.java new file mode 100644 index 00000000000..75fdac01658 --- /dev/null +++ b/video/src/test/java/com/example/video/QuickstartIT.java @@ -0,0 +1,57 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.video; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for video analysis sample. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class QuickstartIT { + 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 test() throws Exception { + QuickstartSample.main(new String[0]); + String got = bout.toString(); + + // Test that the video with a cat has the whiskers label (may change). + assertThat(got.toUpperCase()).contains("VIDEO LABEL DESCRIPTION"); + assertThat(got.toUpperCase()).contains("CONFIDENCE"); + } +} diff --git a/video/src/test/java/video/DetectFacesGcsIT.java b/video/src/test/java/video/DetectFacesGcsIT.java new file mode 100644 index 00000000000..d5e204a5b2a --- /dev/null +++ b/video/src/test/java/video/DetectFacesGcsIT.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package video; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +@SuppressWarnings("checkstyle:AbbreviationAsWordInName") +public class DetectFacesGcsIT { + 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); + } + + @Test + public void testDetectFacesGcs() throws Exception { + DetectFacesGcs.detectFacesGcs("gs://cloud-samples-data/video/googlework_short.mp4"); + String got = bout.toString(); + assertThat(got).contains("Face detected:"); + } +} diff --git a/video/src/test/java/video/DetectFacesIT.java b/video/src/test/java/video/DetectFacesIT.java new file mode 100644 index 00000000000..da47d74efce --- /dev/null +++ b/video/src/test/java/video/DetectFacesIT.java @@ -0,0 +1,55 @@ +/* + * Copyright 2020 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package video; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +@SuppressWarnings("checkstyle:AbbreviationAsWordInName") +public class DetectFacesIT { + + 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); + } + + @Test + public void testDetectFaces() throws Exception { + DetectFaces.detectFaces("resources/googlework_short.mp4"); + String got = bout.toString(); + assertThat(got).contains("Face detected:"); + } +} diff --git a/video/src/test/java/video/DetectIT.java b/video/src/test/java/video/DetectIT.java new file mode 100644 index 00000000000..7837cca4ae2 --- /dev/null +++ b/video/src/test/java/video/DetectIT.java @@ -0,0 +1,118 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package video; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.videointelligence.v1.VideoAnnotationResults; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for video analysis sample. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class DetectIT { + static final String LABEL_GCS_LOCATION = "gs://cloud-samples-data/video/cat.mp4"; + static final String LABEL_FILE_LOCATION = "./resources/googlework_short.mp4"; + static final String SHOTS_FILE_LOCATION = "gs://cloud-samples-data/video/gbikes_dinosaur.mp4"; + static final String EXPLICIT_CONTENT_LOCATION = "gs://cloud-samples-data/video/cat.mp4"; + static final String SPEECH_GCS_LOCATION = + "gs://java-docs-samples-testing/video/googlework_short.mp4"; + + 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); + } + + @Test + public void testLabels() throws Exception { + String[] args = {"labels", LABEL_GCS_LOCATION}; + Detect.argsHelper(args); + String got = bout.toString(); + assertThat(got).contains("Video label"); + } + + @Test + public void testLabelsFile() throws Exception { + String[] args = {"labels-file", LABEL_FILE_LOCATION}; + Detect.argsHelper(args); + String got = bout.toString(); + assertThat(got).contains("Video label"); + } + + @Test + public void testExplicitContent() throws Exception { + String[] args = {"explicit-content", EXPLICIT_CONTENT_LOCATION}; + Detect.argsHelper(args); + String got = bout.toString(); + assertThat(got).contains("Adult:"); + } + + @Test + public void testShots() throws Exception { + String[] args = {"shots", SHOTS_FILE_LOCATION}; + Detect.argsHelper(args); + String got = bout.toString(); + assertThat(got).contains("Shots:"); + assertThat(got).contains("Location:"); + } + + @Test + public void testSpeechTranscription() throws Exception { + String[] args = {"speech-transcription", SPEECH_GCS_LOCATION}; + Detect.argsHelper(args); + String got = bout.toString(); + + assertThat(got).contains("Transcript"); + } + + @Test + public void testTrackObjects() throws Exception { + TrackObjects.trackObjects("resources/googlework_short.mp4"); + + String got = bout.toString(); + + assertThat(got).contains("Entity id"); + } + + @Test + public void testTrackObjectsGcs() throws Exception { + VideoAnnotationResults result = TrackObjects.trackObjectsGcs(LABEL_GCS_LOCATION); + + String got = bout.toString(); + assertThat(got).contains("Entity id"); + } +} diff --git a/video/src/test/java/video/DetectLogoGcsTest.java b/video/src/test/java/video/DetectLogoGcsTest.java new file mode 100644 index 00000000000..e1774aad680 --- /dev/null +++ b/video/src/test/java/video/DetectLogoGcsTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2020 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package video; + +import static com.google.common.truth.Truth.assertThat; + +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.Test; + +public class DetectLogoGcsTest { + 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); + } + + @Test + public void testLogoDetectGcs() + throws IOException, ExecutionException, InterruptedException, TimeoutException { + LogoDetectionGcs.detectLogoGcs("gs://cloud-samples-data/video/googlework_tiny.mp4"); + String got = bout.toString(); + + assertThat(got).contains("Description"); + assertThat(got).contains("Confidence"); + assertThat(got).contains("Start Time Offset"); + assertThat(got).contains("End Time Offset"); + } +} diff --git a/video/src/test/java/video/DetectLogoTest.java b/video/src/test/java/video/DetectLogoTest.java new file mode 100644 index 00000000000..0d91849bb4a --- /dev/null +++ b/video/src/test/java/video/DetectLogoTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2020 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package video; + +import static com.google.common.truth.Truth.assertThat; + +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.Test; + +public class DetectLogoTest { + 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); + } + + @Test + public void testLogoDetect() + throws IOException, ExecutionException, InterruptedException, TimeoutException { + LogoDetection.detectLogo("resources/googlework_short.mp4"); + String got = bout.toString(); + + assertThat(got).contains("Description"); + assertThat(got).contains("Confidence"); + assertThat(got).contains("Start Time Offset"); + assertThat(got).contains("End Time Offset"); + } +} diff --git a/video/src/test/java/video/DetectPersonGcsIT.java b/video/src/test/java/video/DetectPersonGcsIT.java new file mode 100644 index 00000000000..1ffa8fe40cc --- /dev/null +++ b/video/src/test/java/video/DetectPersonGcsIT.java @@ -0,0 +1,55 @@ +/* + * Copyright 2020 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package video; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +@SuppressWarnings("checkstyle:AbbreviationAsWordInName") +public class DetectPersonGcsIT { + + 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); + } + + @Test + public void testDetectPersonGcs() throws Exception { + DetectPersonGcs.detectPersonGcs("gs://cloud-samples-data/video/googlework_short.mp4"); + String got = bout.toString(); + assertThat(got).contains("Landmark"); + } +} diff --git a/video/src/test/java/video/DetectPersonIT.java b/video/src/test/java/video/DetectPersonIT.java new file mode 100644 index 00000000000..12551afedd8 --- /dev/null +++ b/video/src/test/java/video/DetectPersonIT.java @@ -0,0 +1,55 @@ +/* + * Copyright 2020 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package video; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +@SuppressWarnings("checkstyle:AbbreviationAsWordInName") +public class DetectPersonIT { + + 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); + } + + @Test + public void testDetectPerson() throws Exception { + DetectPerson.detectPerson("resources/googlework_short.mp4"); + String got = bout.toString(); + assertThat(got).contains("Landmark"); + } +} diff --git a/video/src/test/java/video/DetectTextTest.java b/video/src/test/java/video/DetectTextTest.java new file mode 100644 index 00000000000..7663860afbf --- /dev/null +++ b/video/src/test/java/video/DetectTextTest.java @@ -0,0 +1,103 @@ +/* + * Copyright 2020 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package video; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.testing.junit4.MultipleAttemptsRule; +import com.google.cloud.videointelligence.v1.TextAnnotation; +import com.google.cloud.videointelligence.v1.VideoAnnotationResults; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.Arrays; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +public class DetectTextTest { + static final String SPEECH_GCS_LOCATION = + "gs://java-docs-samples-testing/video/googlework_short.mp4"; + private static final List POSSIBLE_TEXTS = + Arrays.asList( + "Google", + "SUR", + "SUR", + "ROTO", + "Vice President", + "58oo9", + "LONDRES", + "OMAR", + "PARIS", + "METRO", + "RUE", + "CARLO"); + @Rule public MultipleAttemptsRule multipleAttemptsRule = new MultipleAttemptsRule(3); + private ByteArrayOutputStream bout; + private PrintStream out; + private PrintStream originalPrintStream; + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + originalPrintStream = System.out; + originalPrintStream = System.out; + System.setOut(out); + } + + @After + public void tearDown() { + System.out.flush(); + System.setOut(originalPrintStream); + } + + @Test + public void testTextDetection() throws Exception { + VideoAnnotationResults result = TextDetection.detectText("resources/googlework_short.mp4"); + + boolean textExists = false; + for (TextAnnotation textAnnotation : result.getTextAnnotationsList()) { + for (String possibleText : POSSIBLE_TEXTS) { + if (textAnnotation.getText().toUpperCase().contains(possibleText.toUpperCase())) { + textExists = true; + break; + } + } + } + + assertThat(textExists).isTrue(); + } + + @Test + public void testTextDetectionGcs() throws Exception { + VideoAnnotationResults result = TextDetection.detectTextGcs(SPEECH_GCS_LOCATION); + + boolean textExists = false; + for (TextAnnotation textAnnotation : result.getTextAnnotationsList()) { + for (String possibleText : POSSIBLE_TEXTS) { + if (textAnnotation.getText().toUpperCase().contains(possibleText.toUpperCase())) { + textExists = true; + break; + } + } + } + + assertThat(textExists).isTrue(); + } +} diff --git a/video/src/test/java/video/QuickstartIT.java b/video/src/test/java/video/QuickstartIT.java new file mode 100644 index 00000000000..dcee2841596 --- /dev/null +++ b/video/src/test/java/video/QuickstartIT.java @@ -0,0 +1,61 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package video; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for video analysis sample. */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class QuickstartIT { + 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); + } + + @Test + public void test() throws Exception { + QuickstartSample.main(new String[0]); + String got = bout.toString(); + + // Test that the video with a cat has the whiskers label (may change). + assertThat(got.toUpperCase()).contains("VIDEO LABEL DESCRIPTION"); + assertThat(got.toUpperCase()).contains("CONFIDENCE"); + } +} diff --git a/vision/spring-framework/README.md b/vision/spring-framework/README.md deleted file mode 100644 index 065d7df8e20..00000000000 --- a/vision/spring-framework/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Google Vision Spring Framework Samples - -These samples have moved to [googleapis/java-vision](https://github.com/googleapis/java-vision/tree/main/samples). \ No newline at end of file diff --git a/webrisk/pom.xml b/webrisk/pom.xml new file mode 100644 index 00000000000..72f1548364e --- /dev/null +++ b/webrisk/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + com.example.webrisk + webrisk-snippets + jar + Google Web Risk Snippets + https://github.com/GoogleCloudPlatform/java-docs-samples/tree/main/webrisk + + + + com.google.cloud.samples + shared-configuration + 1.2.0 + + + + 1.8 + 1.8 + UTF-8 + + + + + + + + com.google.cloud + libraries-bom + 26.1.4 + pom + import + + + + + + + com.google.cloud + google-cloud-webrisk + + + + + junit + junit + 4.13.2 + test + + + com.google.truth + truth + 1.1.3 + test + + + diff --git a/webrisk/src/main/java/webrisk/SearchUriExample.java b/webrisk/src/main/java/webrisk/SearchUriExample.java new file mode 100644 index 00000000000..3791a305fbb --- /dev/null +++ b/webrisk/src/main/java/webrisk/SearchUriExample.java @@ -0,0 +1,53 @@ +/* + * 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 webrisk; + +import com.google.cloud.webrisk.v1.WebRiskServiceClient; +import com.google.webrisk.v1.SearchUrisRequest; +import com.google.webrisk.v1.SearchUrisResponse; +import com.google.webrisk.v1.ThreatType; +import java.io.IOException; + +public class SearchUriExample { + + public static void searchUriExample() throws IOException { + // The URL to be searched + String uri = "http://testsafebrowsing.appspot.com/s/malware.html"; + SearchUrisResponse response = searchUriExample(uri); + } + + // [START webrisk_search_uri] + public static SearchUrisResponse searchUriExample(String uri) throws IOException { + // create-webrisk-client + try (WebRiskServiceClient webRiskServiceClient = WebRiskServiceClient.create()) { + // Query the url for a specific threat type + SearchUrisRequest searchUrisRequest = + SearchUrisRequest.newBuilder().addThreatTypes(ThreatType.MALWARE).setUri(uri).build(); + SearchUrisResponse searchUrisResponse = webRiskServiceClient.searchUris(searchUrisRequest); + webRiskServiceClient.shutdownNow(); + if (!searchUrisResponse.getThreat().getThreatTypesList().isEmpty()) { + System.out.println("The URL has the following threat : "); + System.out.println(searchUrisResponse); + } else { + System.out.println("The URL is safe!"); + } + + return searchUrisResponse; + } + } + // [END webrisk_search_uri] +} diff --git a/webrisk/src/main/java/webrisk/SubmitUriExample.java b/webrisk/src/main/java/webrisk/SubmitUriExample.java new file mode 100644 index 00000000000..93fb5604995 --- /dev/null +++ b/webrisk/src/main/java/webrisk/SubmitUriExample.java @@ -0,0 +1,49 @@ +/* + * 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 webrisk; + +import com.google.cloud.webrisk.v1.WebRiskServiceClient; +import com.google.webrisk.v1.CreateSubmissionRequest; +import com.google.webrisk.v1.Submission; +import java.io.IOException; + +public class SubmitUriExample { + + public static void submitUriExample() throws IOException { + // The URL to be submitted + String uri = "http://testsafebrowsing.appspot.com/s/malware.html"; + Submission response = submitUriExample(uri); + } + + // [START webrisk_submit_uri] + public static Submission submitUriExample(String uri) throws IOException { + // create-webrisk-client + try (WebRiskServiceClient webRiskServiceClient = WebRiskServiceClient.create()) { + Submission submission = Submission.newBuilder().setUri(uri).build(); + CreateSubmissionRequest submissionRequest = + CreateSubmissionRequest.newBuilder() + .setParent("projects/your-project-id") + .setSubmission(submission) + .build(); + Submission submissionResponse = webRiskServiceClient.createSubmission(submissionRequest); + webRiskServiceClient.shutdownNow(); + System.out.println("The submitted " + submissionResponse); + return submissionResponse; + } + } + // [END webrisk_submit_uri] +} diff --git a/webrisk/src/test/java/webrisk/SearchUriExampleTest.java b/webrisk/src/test/java/webrisk/SearchUriExampleTest.java new file mode 100644 index 00000000000..0483a1d024d --- /dev/null +++ b/webrisk/src/test/java/webrisk/SearchUriExampleTest.java @@ -0,0 +1,47 @@ +/* + * 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 webrisk; + +import com.google.common.truth.Truth; +import com.google.webrisk.v1.SearchUrisResponse; +import com.google.webrisk.v1.ThreatType; +import java.io.IOException; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class SearchUriExampleTest { + @Test + public void testSearchWithThreat() throws IOException { + // The URL to be searched + String uri = "http://testsafebrowsing.appspot.com/s/malware.html"; + SearchUrisResponse actualResponse = SearchUriExample.searchUriExample(uri); + List type = actualResponse.getThreat().getThreatTypesList(); + Truth.assertThat(type).contains(ThreatType.MALWARE); + } + + @Test + public void testSearchWithoutThreat() throws IOException { + // The URL to be searched + String uri = "http://testsafebrowsing.appspot.com/malware.html"; + SearchUrisResponse actualResponse = SearchUriExample.searchUriExample(uri); + List type = actualResponse.getThreat().getThreatTypesList(); + Truth.assertThat(type).isEmpty(); + } +} diff --git a/webrisk/src/test/java/webrisk/SubmitUriExampleTest.java b/webrisk/src/test/java/webrisk/SubmitUriExampleTest.java new file mode 100644 index 00000000000..6d7c2fba374 --- /dev/null +++ b/webrisk/src/test/java/webrisk/SubmitUriExampleTest.java @@ -0,0 +1,31 @@ +/* + * 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 webrisk; + +import com.google.common.truth.Truth; +import com.google.webrisk.v1.Submission; +import java.io.IOException; +import org.junit.Test; + +public class SubmitUriExampleTest { + @Test + public void testSumbitUriExample() throws IOException { + String testUri = "http://testsafebrowsing.appspot.com/s/malware.html"; + Submission actualSubmission = SubmitUriExample.submitUriExample(testUri); + Truth.assertThat(actualSubmission.getUri()).isEqualTo(testUri); + } +}