");
+ }
+}
diff --git a/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryRun.java b/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryRun.java
new file mode 100644
index 00000000000..fa83033229e
--- /dev/null
+++ b/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryRun.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2018 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.bigquerylogging;
+
+import java.io.IOException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@WebServlet(name = "runQuery BigQuery", value = "/bigquery/run")
+public class BigQueryRun extends HttpServlet {
+ private BigQueryRunner queryRunner;
+
+ public BigQueryRun() throws IOException {
+ this.queryRunner = BigQueryRunner.getInstance();
+ }
+
+ @Override
+ public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ try {
+ queryRunner.runQuery();
+ } catch (InterruptedException e) {
+ resp.sendError(
+ HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Interrupted while running BigQuery job.");
+ }
+ // redirect to home page
+ resp.sendRedirect("/");
+ }
+}
diff --git a/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryRunner.java b/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryRunner.java
new file mode 100644
index 00000000000..0e4702e12ae
--- /dev/null
+++ b/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/BigQueryRunner.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright 2018 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.bigquerylogging;
+
+import com.google.api.Metric;
+import com.google.api.MetricDescriptor;
+import com.google.cloud.ServiceOptions;
+import com.google.cloud.bigquery.BigQuery;
+import com.google.cloud.bigquery.BigQueryOptions;
+import com.google.cloud.bigquery.Job;
+import com.google.cloud.bigquery.JobId;
+import com.google.cloud.bigquery.JobInfo;
+import com.google.cloud.bigquery.QueryJobConfiguration;
+import com.google.cloud.bigquery.TableResult;
+import com.google.cloud.monitoring.v3.MetricServiceClient;
+import com.google.cloud.monitoring.v3.MetricServiceClient.ListMetricDescriptorsPagedResponse;
+import com.google.cloud.monitoring.v3.MetricServiceClient.ListTimeSeriesPagedResponse;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.monitoring.v3.CreateMetricDescriptorRequest;
+import com.google.monitoring.v3.CreateTimeSeriesRequest;
+import com.google.monitoring.v3.ListMetricDescriptorsRequest;
+import com.google.monitoring.v3.ListTimeSeriesRequest;
+import com.google.monitoring.v3.Point;
+import com.google.monitoring.v3.TimeInterval;
+import com.google.monitoring.v3.TimeSeries;
+import com.google.monitoring.v3.TypedValue;
+import com.google.protobuf.util.Timestamps;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+public class BigQueryRunner {
+ private static final String CUSTOM_METRIC_FILTER =
+ "metric.type = starts_with(\"custom.googleapis.com/\")";
+ private static BigQueryRunner instance;
+
+ private static final MetricDescriptor QUERY_DURATION_METRIC =
+ MetricDescriptor.newBuilder()
+ .setName("custom.googleapis.com/queryDuration")
+ .setType("custom.googleapis.com/queryDuration")
+ .setDisplayName("queryDuration")
+ .setDescription("Time it took a query to run.")
+ .setMetricKind(MetricDescriptor.MetricKind.GAUGE)
+ .setValueType(MetricDescriptor.ValueType.INT64)
+ .build();
+ private static final MetricDescriptor ROWS_RETURNED_METRIC =
+ MetricDescriptor.newBuilder()
+ .setName("custom.googleapis.com/rowsReturned")
+ .setType("custom.googleapis.com/rowsReturned")
+ .setDisplayName("rowsReturned")
+ .setDescription("Total rows returned by the query result.")
+ .setMetricKind(MetricDescriptor.MetricKind.GAUGE)
+ .setValueType(MetricDescriptor.ValueType.INT64)
+ .build();
+ private static final Set REQUIRED_METRICS =
+ ImmutableSet.of(QUERY_DURATION_METRIC, ROWS_RETURNED_METRIC);
+
+ private static TableResult mostRecentRunResult;
+ private static Set existingMetrics = Sets.newHashSet();
+
+ private final MetricServiceClient client;
+ private final BigQuery bigquery;
+ private final String projectName;
+ private PrintStream os;
+
+ // Retrieve a singleton instance
+ public static synchronized BigQueryRunner getInstance() throws IOException {
+ if (instance == null) {
+ instance = new BigQueryRunner();
+ }
+ return instance;
+ }
+
+ private BigQueryRunner() throws IOException {
+ this(
+ MetricServiceClient.create(),
+ BigQueryOptions.getDefaultInstance().getService(),
+ System.out);
+ }
+
+ BigQueryRunner(MetricServiceClient metricsClient, BigQuery bigquery, PrintStream os) {
+ client = metricsClient;
+ this.os = os;
+ this.projectName = String.format("projects/%s", ServiceOptions.getDefaultProjectId());
+ this.bigquery = bigquery;
+ }
+
+ public static TableResult getMostRecentRunResult() {
+ return mostRecentRunResult;
+ }
+
+ public void runQuery() throws InterruptedException {
+ QueryJobConfiguration queryConfig =
+ QueryJobConfiguration.newBuilder(
+ "SELECT "
+ + "CONCAT('https://stackoverflow.com/questions/', CAST(id as STRING)) as url, "
+ + "view_count "
+ + "FROM `bigquery-public-data.stackoverflow.posts_questions` "
+ + "WHERE tags like '%google-bigquery%' "
+ + "ORDER BY favorite_count DESC LIMIT 10")
+ // Use standard SQL syntax for queries.
+ // See: https://cloud.google.com/bigquery/sql-reference/
+ .setUseLegacySql(false)
+ .build();
+
+ List timeSeriesList = new ArrayList<>();
+
+ long queryStartTime = System.currentTimeMillis();
+
+ // Create a job ID so that we can safely retry.
+ JobId jobId = JobId.of(UUID.randomUUID().toString());
+ Job queryJob = bigquery.create(JobInfo.newBuilder(queryConfig).setJobId(jobId).build());
+
+ // Wait for the query to complete.
+ queryJob = queryJob.waitFor();
+
+ // Check for errors
+ if (queryJob == null) {
+ throw new RuntimeException("Job no longer exists");
+ } else if (queryJob.getStatus().getError() != null) {
+ // You can also look at queryJob.getStatus().getExecutionErrors() for all
+ // errors, not just the latest one.
+ throw new RuntimeException(queryJob.getStatus().getError().toString());
+ }
+
+ // Log the result metrics.
+ TableResult result = queryJob.getQueryResults();
+
+ long queryEndTime = System.currentTimeMillis();
+ // Add query duration metric.
+ timeSeriesList.add(prepareMetric(QUERY_DURATION_METRIC, queryEndTime - queryStartTime));
+
+ // Add rows returned metric.
+ timeSeriesList.add(prepareMetric(ROWS_RETURNED_METRIC, result.getTotalRows()));
+
+ // Prepares the time series request
+ CreateTimeSeriesRequest request =
+ CreateTimeSeriesRequest.newBuilder()
+ .setName(projectName)
+ .addAllTimeSeries(timeSeriesList)
+ .build();
+
+ createMetricsIfNeeded();
+ client.createTimeSeries(request);
+ os.println("Done writing metrics.");
+
+ mostRecentRunResult = result;
+ }
+
+ // Returns a metric time series with a single int64 data point.
+ private TimeSeries prepareMetric(MetricDescriptor requiredMetric, long metricValue) {
+ TimeInterval interval =
+ TimeInterval.newBuilder()
+ .setEndTime(Timestamps.fromMillis(System.currentTimeMillis()))
+ .build();
+ TypedValue value = TypedValue.newBuilder().setInt64Value(metricValue).build();
+
+ Point point = Point.newBuilder().setInterval(interval).setValue(value).build();
+
+ List pointList = Lists.newArrayList();
+ pointList.add(point);
+
+ Metric metric = Metric.newBuilder().setType(requiredMetric.getName()).build();
+
+ return TimeSeries.newBuilder().setMetric(metric).addAllPoints(pointList).build();
+ }
+
+ public List> getTimeSeriesValues() {
+ List> summaries = Lists.newArrayList();
+ createMetricsIfNeeded();
+ for (MetricDescriptor metric : REQUIRED_METRICS) {
+ ListTimeSeriesRequest listTimeSeriesRequest =
+ ListTimeSeriesRequest.newBuilder()
+ .setName(projectName)
+ .setFilter(String.format("metric.type = \"%s\"", metric.getType()))
+ .setInterval(
+ TimeInterval.newBuilder()
+ .setStartTime(
+ Timestamps.subtract(
+ Timestamps.fromMillis(System.currentTimeMillis()),
+ com.google.protobuf.Duration.newBuilder()
+ .setSeconds(60L * 60L * 24L * 30L) // 30 days ago
+ .build()))
+ .setEndTime(Timestamps.fromMillis(System.currentTimeMillis()))
+ .build())
+ .build();
+ try {
+ ListTimeSeriesPagedResponse listTimeSeriesResponse =
+ client.listTimeSeries(listTimeSeriesRequest);
+ ArrayList timeSeries = Lists.newArrayList(listTimeSeriesResponse.iterateAll());
+ summaries.addAll(
+ timeSeries.stream()
+ .map(TimeSeriesSummary::fromTimeSeries)
+ .collect(Collectors.toList()));
+ } catch (RuntimeException ex) {
+ os.println("MetricDescriptors not yet synced. Please try again in a moment.");
+ }
+ }
+ return summaries;
+ }
+
+ private void createMetricsIfNeeded() {
+ // If all required metrics already exist, no need to make service calls.
+ if (REQUIRED_METRICS.stream()
+ .map(MetricDescriptor::getDisplayName)
+ .allMatch(existingMetrics::contains)) {
+ return;
+ }
+ ListMetricDescriptorsRequest listMetricsRequest =
+ ListMetricDescriptorsRequest.newBuilder()
+ .setName(projectName)
+ .setFilter(CUSTOM_METRIC_FILTER)
+ .build();
+ ListMetricDescriptorsPagedResponse listMetricsResponse =
+ client.listMetricDescriptors(listMetricsRequest);
+
+ for (MetricDescriptor existingMetric : listMetricsResponse.iterateAll()) {
+ existingMetrics.add(existingMetric.getDisplayName());
+ }
+
+ REQUIRED_METRICS.stream()
+ .filter(metric -> !existingMetrics.contains(metric.getDisplayName()))
+ .forEach(this::createMetric);
+ }
+
+ private void createMetric(MetricDescriptor newMetric) {
+ CreateMetricDescriptorRequest request =
+ CreateMetricDescriptorRequest.newBuilder()
+ .setName(projectName)
+ .setMetricDescriptor(newMetric)
+ .build();
+
+ client.createMetricDescriptor(request);
+ }
+}
diff --git a/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/TimeSeriesSummary.java b/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/TimeSeriesSummary.java
new file mode 100644
index 00000000000..04e2e23e543
--- /dev/null
+++ b/appengine-java21/ee8/bigquery/src/main/java/com/example/appengine/bigquerylogging/TimeSeriesSummary.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2018 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.bigquerylogging;
+
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Lists;
+import com.google.monitoring.v3.Point;
+import com.google.monitoring.v3.TimeSeries;
+import com.google.protobuf.Timestamp;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public abstract class TimeSeriesSummary {
+ private String name;
+ private Timestamp mostRecentRunTime;
+ T mostRecentValue;
+ List values;
+
+ public static TimeSeriesSummary> fromTimeSeries(TimeSeries timeSeries) {
+ switch (timeSeries.getValueType()) {
+ case STRING:
+ return new StringTimeSeriesSummary(timeSeries);
+ case INT64:
+ return new Int64TimeSeriesSummary(timeSeries);
+ default:
+ return null;
+ }
+ }
+
+ private TimeSeriesSummary(TimeSeries timeSeries) {
+ name = timeSeries.getMetric().getType();
+ }
+
+ Point getMostRecentPoint(TimeSeries timeSeries) {
+ Point max =
+ Collections.max(
+ timeSeries.getPointsList(),
+ Comparator.comparingLong(p -> p.getInterval().getEndTime().getSeconds()));
+ mostRecentRunTime = max.getInterval().getEndTime();
+ return max;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public T getMostRecentValue() {
+ return mostRecentValue;
+ }
+
+ public Timestamp getMostRecentRunTime() {
+ return mostRecentRunTime;
+ }
+
+ public List getValues() {
+ return values;
+ }
+
+ public abstract T getAverage();
+
+ public static class StringTimeSeriesSummary extends TimeSeriesSummary {
+ private StringTimeSeriesSummary(TimeSeries timeSeries) {
+ super(timeSeries);
+ Point max = getMostRecentPoint(timeSeries);
+ if (max == null) {
+ return;
+ }
+ mostRecentValue = max.getValue().getStringValue();
+ values =
+ Lists.newArrayList(
+ Collections2.transform(
+ timeSeries.getPointsList(), point -> point.getValue().getStringValue()));
+ }
+
+ @Override
+ public String getAverage() {
+ return values.stream().collect(Collectors.joining(","));
+ }
+ }
+
+ public static class Int64TimeSeriesSummary extends TimeSeriesSummary {
+ private Int64TimeSeriesSummary(TimeSeries timeSeries) {
+ super(timeSeries);
+ Point max = getMostRecentPoint(timeSeries);
+ if (max == null) {
+ return;
+ }
+ mostRecentValue = max.getValue().getInt64Value();
+ values =
+ Lists.newArrayList(
+ Collections2.transform(
+ timeSeries.getPointsList(), point -> point.getValue().getInt64Value()));
+ }
+
+ @Override
+ public Long getAverage() {
+ return values.stream().collect(Collectors.averagingLong(Long::longValue)).longValue();
+ }
+ }
+}
diff --git a/appengine-java21/ee8/bigquery/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java21/ee8/bigquery/src/main/webapp/WEB-INF/appengine-web.xml
new file mode 100644
index 00000000000..15fc6f754c4
--- /dev/null
+++ b/appengine-java21/ee8/bigquery/src/main/webapp/WEB-INF/appengine-web.xml
@@ -0,0 +1,10 @@
+
+
+ java21
+ true
+
+
+
+
+ dzlier-work
+
\ No newline at end of file
diff --git a/appengine-java21/ee8/bigquery/src/main/webapp/index.jsp b/appengine-java21/ee8/bigquery/src/main/webapp/index.jsp
new file mode 100644
index 00000000000..af045fbf1fc
--- /dev/null
+++ b/appengine-java21/ee8/bigquery/src/main/webapp/index.jsp
@@ -0,0 +1,30 @@
+<%@ page import="com.example.appengine.bigquerylogging.BigQueryHome" %>
+
+
+ An example of using BigQuery and StackDriver Logging on AppEngine Standard
+
+
Run query
+
+
Most Recent Run Results
+
+
+
URL
+
View Count
+
+ <%= BigQueryHome.getMostRecentRun() %>
+
+
Run Metric Values
+
+
+
Metric Type
+
Count
+
Most Recent End Time
+
Most Recent Value
+
Average/Values
+
+ <%= BigQueryHome.getMetricAverages() %>
+
+
+
diff --git a/appengine-java21/ee8/bigquery/src/test/java/com/example/appengine/bigquerylogging/BigQueryRunnerTest.java b/appengine-java21/ee8/bigquery/src/test/java/com/example/appengine/bigquerylogging/BigQueryRunnerTest.java
new file mode 100644
index 00000000000..1332c3afa05
--- /dev/null
+++ b/appengine-java21/ee8/bigquery/src/test/java/com/example/appengine/bigquerylogging/BigQueryRunnerTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2018 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.bigquerylogging;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.api.MetricDescriptor;
+import com.google.api.gax.rpc.UnaryCallable;
+import com.google.cloud.bigquery.BigQueryOptions;
+import com.google.cloud.monitoring.v3.MetricServiceClient;
+import com.google.cloud.monitoring.v3.MetricServiceClient.ListMetricDescriptorsPagedResponse;
+import com.google.cloud.monitoring.v3.stub.MetricServiceStub;
+import com.google.monitoring.v3.CreateMetricDescriptorRequest;
+import com.google.monitoring.v3.CreateTimeSeriesRequest;
+import com.google.monitoring.v3.ListMetricDescriptorsRequest;
+import com.google.protobuf.Empty;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Tests for simple app sample. */
+@RunWith(JUnit4.class)
+public class BigQueryRunnerTest {
+ private ByteArrayOutputStream bout;
+ private BigQueryRunner app;
+
+ @Mock private MetricServiceStub metricsServiceStub;
+
+ @Mock
+ private UnaryCallable
+ listCallable;
+
+ @Mock private UnaryCallable createMetricCallable;
+ @Mock private UnaryCallable createTimeSeriesCallable;
+ @Mock private ListMetricDescriptorsPagedResponse listResponse;
+
+ @Captor private ArgumentCaptor createTimeSeriesRequest;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.openMocks(this);
+ bout = new ByteArrayOutputStream();
+ PrintStream out = new PrintStream(bout);
+
+ MetricServiceClient metricsClient = MetricServiceClient.create(metricsServiceStub);
+ app = new BigQueryRunner(metricsClient, BigQueryOptions.getDefaultInstance().getService(), out);
+
+ when(metricsServiceStub.listMetricDescriptorsPagedCallable()).thenReturn(listCallable);
+ when(listCallable.call(any(ListMetricDescriptorsRequest.class))).thenReturn(listResponse);
+ when(listResponse.iterateAll()).thenReturn(Collections.emptyList());
+
+ when(metricsServiceStub.createMetricDescriptorCallable()).thenReturn(createMetricCallable);
+ when(createMetricCallable.call(any(CreateMetricDescriptorRequest.class))).thenReturn(null);
+
+ when(metricsServiceStub.createTimeSeriesCallable()).thenReturn(createTimeSeriesCallable);
+ when(createTimeSeriesCallable.call(any(CreateTimeSeriesRequest.class)))
+ .thenReturn(Empty.getDefaultInstance());
+ }
+
+ @Test
+ public void testRun() throws Exception {
+ app.runQuery();
+ String got = bout.toString();
+ assertThat(got).contains("Done writing metrics.");
+ verify(metricsServiceStub).listMetricDescriptorsPagedCallable();
+
+ verify(metricsServiceStub, times(2)).createMetricDescriptorCallable();
+
+ verify(metricsServiceStub).createTimeSeriesCallable();
+ verify(createTimeSeriesCallable).call(createTimeSeriesRequest.capture());
+ CreateTimeSeriesRequest actual = createTimeSeriesRequest.getValue();
+ assertEquals(2, actual.getTimeSeriesCount());
+ assertThat(actual.getTimeSeries(0).getMetric().getType())
+ .isEqualTo("custom.googleapis.com/queryDuration");
+ assertThat(actual.getTimeSeries(0).getPoints(0).getValue().getInt64Value()).isGreaterThan(0L);
+ assertThat(actual.getTimeSeries(1).getMetric().getType())
+ .isEqualTo("custom.googleapis.com/rowsReturned");
+ assertThat(actual.getTimeSeries(1).getPoints(0).getValue().getInt64Value()).isGreaterThan(0L);
+ }
+}
diff --git a/appengine-java21/ee8/bigtable/README.md b/appengine-java21/ee8/bigtable/README.md
new file mode 100644
index 00000000000..c4df5547773
--- /dev/null
+++ b/appengine-java21/ee8/bigtable/README.md
@@ -0,0 +1,59 @@
+Bigtable-hello-j21
+=================
+
+
+
+
+Moves the Bigtable Hello World application to Google App Engine Standard for Java 21.
+
+
+* [Java 21](http://www.oracle.com/technetwork/java/javase/downloads/index.html)
+* [Maven](https://maven.apache.org/download.cgi) (at least 3.3.9)
+* [Gradle](https://gradle.org)
+* [Google Cloud CLI](https://cloud.google.com/cli/) (aka gcloud)
+
+Initialize the Google Cloud CLI using:
+
+ gcloud init
+
+ gcloud auth application-default login
+
+Then you need to [Create a Cloud Bigtable Instance](https://cloud.google.com/bigtable/docs/creating-instance)
+
+
+## Using Maven
+
+### Run Locally
+
+ mvn -Dbigtable.projectID=PROJECTID -Dbigtable.instanceID=INSTANCEID appengine:run
+
+### Deploy to App Engine Standard for Java 21
+
+ mvn -Dbigtable.projectID=PROJECTID -Dbigtable.instanceID=INSTANCEID package appengine:deploy
+
+### Run Integration Tests
+
+ mvn -Dbigtable.projectID=PROJECTID -Dbigtable.instanceID=INSTANCEID verify
+
+## Using Gradle
+
+### Run Locally
+
+ gradle -Dbigtable.projectID=PROJECTID -Dbigtable.instanceID=INSTANCEID appengineRun
+
+### Integration Tests & Deploy to App Engine Standard for Java 21
+
+ gradle -Dbigtable.projectID=PROJECTID -Dbigtable.instanceID=INSTANCEID appengineDeploy
+
+As you add / modify the source code (`src/main/java/...`) it's very useful to add
+[unit testing](https://cloud.google.com/appengine/docs/java/tools/localunittesting)
+to (`src/main/test/...`). The following resources are quite useful:
+
+* [JUnit4](http://junit.org/junit4/)
+* [Mockito](http://mockito.org/)
+* [Truth](http://google.github.io/truth/)
+
+### When done
+
+Cloud Bigtable Instances should be [deleted](https://cloud.google.com/bigtable/docs/deleting-instance)
+when they are no longer being used as they use significant resources.
diff --git a/appengine-java21/ee8/bigtable/build.gradle b/appengine-java21/ee8/bigtable/build.gradle
new file mode 100644
index 00000000000..999819d2758
--- /dev/null
+++ b/appengine-java21/ee8/bigtable/build.gradle
@@ -0,0 +1,113 @@
+// Copyright 2017 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// [START gae_java21_bigtable_gradle_file]
+buildscript { // Configuration for building
+ repositories {
+ jcenter() // Bintray's repository - a fast Maven Central mirror & more
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.google.cloud.tools:appengine-gradle-plugin:2.5.0'
+ classpath 'org.akhikhl.gretty:gretty:+'
+ }
+}
+
+apply plugin: 'java'
+apply plugin: 'war'
+apply plugin: 'org.akhikhl.gretty' // To get webappcopy
+apply plugin: 'com.google.cloud.tools.appengine'
+
+group = 'com.example.google.cloud.bigtable'
+version = '0.1-SNAPSHOT'
+
+sourceCompatibility = 21
+targetCompatibility = 21
+
+tasks.withType(JavaCompile) {
+ options.encoding = 'UTF-8'
+}
+
+repositories {
+ maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
+ jcenter()
+ mavenCentral()
+}
+
+dependencies {
+ compile group: 'com.google.cloud.bigtable', name: 'bigtable-hbase-1.2', version:'1.0.0-pre3'
+ compile group: 'org.apache.hbase', name: 'hbase-client', version:'2.5.6'
+ compile group: 'io.netty', name: 'netty-tcnative-boringssl-static', version:'2.0.62.Final'
+ compile group: 'jakarta.servlet.jsp.jstl', name: 'jakarta.servlet.jsp.jstl-api', version:'1.2.7'
+
+ providedCompile group: 'jakarta.servlet', name: 'jakarta.servlet-api', version:'4.0.4'
+
+ testCompile group: 'com.google.truth', name: 'truth', version:'1.4.4'
+ testCompile group: 'junit', name: 'junit', version:'4.13.2'
+ testCompile group: 'org.mockito', name: 'mockito-core', version:'4.11.0'
+}
+
+import org.apache.tools.ant.filters.ReplaceTokens
+gretty {
+ contextPath = '/'
+ servletContainer = 'jetty9'
+
+ jvmArgs = [ '-DBIGTABLE_PROJECT=' + System.getProperty("bigtable.projectID"),
+ '-DBIGTABLE_INSTANCE=' + System.getProperty("bigtable.instanceID")]
+
+ webappCopy {
+ // Enable filtering on all xml files in WEB-INF
+ filesMatching "**/WEB-INF/*.xml", { FileCopyDetails fileDetails ->
+ logger.lifecycle 'File filtered: {}', fileDetails.path
+ filter (ReplaceTokens, tokens: [
+ 'bigtable.projectID' : System.getProperty("bigtable.projectID"),
+ 'bigtable.instanceID': System.getProperty("bigtable.instanceID")
+ ])
+ }
+ }
+}
+
+// Always run unit tests
+appengineDeploy.dependsOn test
+
+// [START gae_java21_bigtable_gradle_model]
+appengine {
+ run {
+
+ }
+ deploy { // deploy configuration
+ stopPreviousVersion = true // default - stop the current version
+ promote = true // default - & make this the current version
+ }
+}
+
+test {
+ useJUnit()
+ testLogging.showStandardStreams = true
+
+ systemProperty 'BIGTABLE_PROJECT', System.getProperty("bigtable.projectID")
+ systemProperty 'BIGTABLE_INSTANCE',System.getProperty("bigtable.instanceID")
+
+ beforeTest { descriptor ->
+ logger.lifecycle("test: " + descriptor + " Running")
+ }
+
+ onOutput { descriptor, event ->
+ logger.lifecycle("test: " + descriptor + ": " + event.message )
+ }
+ afterTest { descriptor, result ->
+ logger.lifecycle("test: " + descriptor + ": " + result )
+ }
+}
+// [END gae_java21_bigtable_gradle_model]
+// [END gae_java21_bigtable_gradle_file]
diff --git a/appengine-java21/ee8/bigtable/gradle/wrapper/gradle-wrapper.properties b/appengine-java21/ee8/bigtable/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000000..6e82a0b51fb
--- /dev/null
+++ b/appengine-java21/ee8/bigtable/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Apr 03 21:11:48 PDT 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
diff --git a/appengine-java21/ee8/bigtable/gradlew b/appengine-java21/ee8/bigtable/gradlew
new file mode 100755
index 00000000000..4453ccea33d
--- /dev/null
+++ b/appengine-java21/ee8/bigtable/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save ( ) {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/appengine-java21/ee8/bigtable/gradlew.bat b/appengine-java21/ee8/bigtable/gradlew.bat
new file mode 100644
index 00000000000..e95643d6a2c
--- /dev/null
+++ b/appengine-java21/ee8/bigtable/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/appengine-java21/ee8/bigtable/pom.xml b/appengine-java21/ee8/bigtable/pom.xml
new file mode 100644
index 00000000000..8a52e798a79
--- /dev/null
+++ b/appengine-java21/ee8/bigtable/pom.xml
@@ -0,0 +1,190 @@
+
+
+
+
+ 4.0.0
+ war
+ 0.1-SNAPSHOT
+
+ com.example.appengine
+ bigtable-hello-j21
+
+
+
+ com.google.cloud.samples
+ shared-configuration
+ 1.2.0
+
+
+
+ 21
+ 21
+
+ YOUR_PROJECT_ID
+ YOUR_INSTANCE_ID
+ false
+
+
+
+
+ com.google.cloud.bigtable
+
+ bigtable-hbase-1.x-hadoop
+ 2.12.0
+
+
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ 4.0.4
+ jar
+ provided
+
+
+ jakarta.servlet.jsp.jstl
+ jakarta.servlet.jsp.jstl-api
+ 1.2.7
+
+
+
+
+
+ com.google.truth
+ truth
+ 1.4.4
+ test
+
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+ org.mockito
+ mockito-core
+ 4.11.0
+ test
+
+
+
+
+
+ ${project.build.directory}/${project.build.finalName}/WEB-INF/classes
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 3.3.1
+
+ UTF-8
+
+ @
+
+ false
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+ 3.4.0
+
+ true
+
+
+
+ ${basedir}/src/main/webapp/WEB-INF
+ true
+ WEB-INF
+
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.13
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ 3.2.2
+
+
+ ${bigtable.projectID}
+ ${bigtable.instanceID}
+
+
+
+
+
+ com.google.cloud.tools
+ appengine-maven-plugin
+ 2.8.0
+
+ GCLOUD_CONFIG
+ GCLOUD_CONFIG
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+
+
+ org.apache.maven.plugins
+ maven-clean-plugin
+ 3.3.2
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+ 3.4.1
+
+
+
+ [3.5.0,)
+
+
+
+
+
+
+
+
+
+ snapshots-repo
+ https://oss.sonatype.org/content/repositories/snapshots
+ false
+ true
+
+
+
+
diff --git a/appengine-java21/ee8/bigtable/settings.gradle b/appengine-java21/ee8/bigtable/settings.gradle
new file mode 100644
index 00000000000..f77c416bd3a
--- /dev/null
+++ b/appengine-java21/ee8/bigtable/settings.gradle
@@ -0,0 +1,14 @@
+// Copyright 2017 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+rootProject.name = 'bigtable-hello-j21'
diff --git a/appengine-java21/ee8/bigtable/src/main/java/com/example/bigtable/BigtableHelloWorld.java b/appengine-java21/ee8/bigtable/src/main/java/com/example/bigtable/BigtableHelloWorld.java
new file mode 100644
index 00000000000..3d6e07dae5a
--- /dev/null
+++ b/appengine-java21/ee8/bigtable/src/main/java/com/example/bigtable/BigtableHelloWorld.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2016 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.example.bigtable;
+
+// [START gae_java21_bigtable_helloworld]
+
+import java.io.IOException;
+import org.apache.hadoop.hbase.HColumnDescriptor;
+import org.apache.hadoop.hbase.HTableDescriptor;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.client.Admin;
+import org.apache.hadoop.hbase.client.Connection;
+import org.apache.hadoop.hbase.client.Get;
+import org.apache.hadoop.hbase.client.Put;
+import org.apache.hadoop.hbase.client.Result;
+import org.apache.hadoop.hbase.client.ResultScanner;
+import org.apache.hadoop.hbase.client.Scan;
+import org.apache.hadoop.hbase.client.Table;
+import org.apache.hadoop.hbase.util.Bytes;
+
+/**
+ * A minimal application that connects to Cloud Bigtable using the native HBase API and performs
+ * some basic operations.
+ */
+public class BigtableHelloWorld {
+
+ // Refer to table metadata names by byte array in the HBase API
+ private static final byte[] TABLE_NAME = Bytes.toBytes("Hello-Bigtable");
+ private static final byte[] COLUMN_FAMILY_NAME = Bytes.toBytes("cf1");
+ private static final byte[] COLUMN_NAME = Bytes.toBytes("greeting");
+
+ // Write some friendly greetings to Cloud Bigtable
+ private static final String[] GREETINGS = {
+ "Hello World!", "Hello Cloud Bigtable!", "Hello HBase!"
+ };
+
+ /**
+ * Create a table -- first time only.
+ *
+ * @param connection to Bigtable
+ * @return the status
+ */
+ public static String create(Connection connection) {
+ try {
+ // The admin API lets us create, manage and delete tables
+ Admin admin = connection.getAdmin();
+
+ // Create a table with a single column family
+ HTableDescriptor descriptor = new HTableDescriptor(TableName.valueOf(TABLE_NAME));
+ descriptor.addFamily(new HColumnDescriptor(COLUMN_FAMILY_NAME));
+
+ admin.createTable(descriptor);
+ } catch (IOException e) {
+ return "Table exists.";
+ }
+ return "Create table " + Bytes.toString(TABLE_NAME);
+ }
+
+ /** Connects to Cloud Bigtable, runs some basic operations and prints the results. */
+ public static String doHelloWorld() {
+
+ StringBuilder result = new StringBuilder();
+
+ // Create the Bigtable connection, use try-with-resources to make sure it gets closed
+ Connection connection = BigtableHelper.getConnection();
+ result.append(create(connection));
+ result.append("
");
+ try (Table table = connection.getTable(TableName.valueOf(TABLE_NAME))) {
+
+ // Retrieve the table we just created so we can do some reads and writes
+
+ // Write some rows to the table
+ result.append("Write some greetings to the table ");
+ for (int i = 0; i < GREETINGS.length; i++) {
+ // Each row has a unique row key.
+ //
+ // Note: This example uses sequential numeric IDs for simplicity, but
+ // this can result in poor performance in a production application.
+ // Since rows are stored in sorted order by key, sequential keys can
+ // result in poor distribution of operations across nodes.
+ //
+ // For more information about how to design a Bigtable schema for the
+ // best performance, see the documentation:
+ //
+ // https://cloud.google.com/bigtable/docs/schema-design
+ String rowKey = "greeting" + i;
+
+ // Put a single row into the table. We could also pass a list of Puts to write a batch.
+ Put put = new Put(Bytes.toBytes(rowKey));
+ put.addColumn(COLUMN_FAMILY_NAME, COLUMN_NAME, Bytes.toBytes(GREETINGS[i]));
+ table.put(put);
+ }
+
+ // Get the first greeting by row key
+ String rowKey = "greeting0";
+ Result getResult = table.get(new Get(Bytes.toBytes(rowKey)));
+ String greeting = Bytes.toString(getResult.getValue(COLUMN_FAMILY_NAME, COLUMN_NAME));
+ result.append("Get a single greeting by row key ");
+
+ result.append(" ");
+ result.append(rowKey);
+ result.append("= ");
+ result.append(greeting);
+ result.append(" ");
+
+ // Now scan across all rows.
+ Scan scan = new Scan();
+
+ result.append("Scan for all greetings:");
+ ResultScanner scanner = table.getScanner(scan);
+ for (Result row : scanner) {
+ byte[] valueBytes = row.getValue(COLUMN_FAMILY_NAME, COLUMN_NAME);
+ result.append(" ");
+ result.append(Bytes.toString(valueBytes));
+ result.append(" ");
+ }
+
+ } catch (IOException e) {
+ result.append("Exception while running HelloWorld: " + e.getMessage() + " ");
+ result.append(e.toString());
+ return result.toString();
+ }
+
+ return result.toString();
+ }
+}
+// [END gae_java21_bigtable_helloworld]
diff --git a/appengine-java21/ee8/bigtable/src/main/java/com/example/bigtable/BigtableHelper.java b/appengine-java21/ee8/bigtable/src/main/java/com/example/bigtable/BigtableHelper.java
new file mode 100644
index 00000000000..dfcbd1e6352
--- /dev/null
+++ b/appengine-java21/ee8/bigtable/src/main/java/com/example/bigtable/BigtableHelper.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2016 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.bigtable;
+
+import com.google.cloud.bigtable.hbase.BigtableConfiguration;
+import java.io.IOException;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.annotation.WebListener;
+import org.apache.hadoop.hbase.client.Connection;
+
+/**
+ * BigtableHelper, a ServletContextListener, is setup in web.xml to run before a jsp is run. Project
+ * / Instance settings can be passed as an Environment Variable, a System Property, or set in
+ * web.xml from a context-param
+ */
+@WebListener
+public class BigtableHelper implements ServletContextListener {
+
+ private static String PROJECT_ID;
+ private static String INSTANCE_ID;
+
+ // The initial connection to Cloud Bigtable is an expensive operation -- We cache this Connection
+ // to speed things up. For this sample, keeping them here is a good idea, for
+ // your application, you may wish to keep this somewhere else.
+ private static Connection connection = null; // The authenticated connection
+
+ private static ServletContext sc;
+
+ /** Connect will establish the connection to Cloud Bigtable. */
+ public static void connect() throws IOException {
+
+ if (PROJECT_ID == null || INSTANCE_ID == null) {
+ if (sc != null) {
+ sc.log("environment variables BIGTABLE_PROJECT, and BIGTABLE_INSTANCE need to be defined.");
+ }
+ return;
+ }
+
+ connection = BigtableConfiguration.connect(PROJECT_ID, INSTANCE_ID);
+ }
+
+ /**
+ * Get the shared connection to Cloud Bigtable.
+ *
+ * @return the connection
+ */
+ public static Connection getConnection() {
+ if (connection == null) {
+ try {
+ connect();
+ } catch (IOException e) {
+ if (sc != null) {
+ sc.log("connect ", e);
+ }
+ }
+ }
+ if (connection == null) {
+ if (sc != null) {
+ sc.log("BigtableHelper-No Connection");
+ }
+ }
+ return connection;
+ }
+
+ @Override
+ public void contextInitialized(ServletContextEvent event) {
+ // This will be invoked as part of a warmup request, or the first user
+ // request if no warmup request was invoked.
+
+ if (event != null) {
+ sc = event.getServletContext();
+ if (PROJECT_ID == null) {
+ PROJECT_ID = sc.getInitParameter("BIGTABLE_PROJECT");
+ }
+ if (INSTANCE_ID == null) {
+ INSTANCE_ID = sc.getInitParameter("BIGTABLE_INSTANCE");
+ }
+ }
+
+ if (PROJECT_ID != null && PROJECT_ID.startsWith("@")) {
+ PROJECT_ID = null;
+ }
+ if (INSTANCE_ID != null && INSTANCE_ID.startsWith("@")) {
+ INSTANCE_ID = null;
+ }
+
+ if (PROJECT_ID == null) {
+ PROJECT_ID = System.getProperty("bigtable.projectID");
+ }
+ if (INSTANCE_ID == null) {
+ INSTANCE_ID = System.getProperty("bigtable.instanceID");
+ }
+
+ try {
+ connect();
+ } catch (IOException e) {
+ if (sc != null) {
+ sc.log("BigtableHelper - connect ", e);
+ }
+ }
+ if (connection == null) {
+ if (sc != null) {
+ sc.log("BigtableHelper-No Connection");
+ }
+ }
+ if (sc != null) {
+ sc.log("ctx Initialized: " + PROJECT_ID + " " + INSTANCE_ID);
+ }
+ }
+
+ @Override
+ public void contextDestroyed(ServletContextEvent event) {
+ // App Engine does not currently invoke this method.
+ if (connection == null) {
+ return;
+ }
+ try {
+ connection.close();
+ } catch (IOException io) {
+ if (sc != null) {
+ sc.log("contextDestroyed ", io);
+ }
+ }
+ connection = null;
+ }
+}
diff --git a/appengine-java21/ee8/bigtable/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java21/ee8/bigtable/src/main/webapp/WEB-INF/appengine-web.xml
new file mode 100644
index 00000000000..2209fe610cd
--- /dev/null
+++ b/appengine-java21/ee8/bigtable/src/main/webapp/WEB-INF/appengine-web.xml
@@ -0,0 +1,24 @@
+
+
+
+ java21
+ true
+
+
+
+
+
+
+
+
diff --git a/appengine-java21/ee8/bigtable/src/main/webapp/WEB-INF/logging.properties b/appengine-java21/ee8/bigtable/src/main/webapp/WEB-INF/logging.properties
new file mode 100644
index 00000000000..0c2ea51bc6d
--- /dev/null
+++ b/appengine-java21/ee8/bigtable/src/main/webapp/WEB-INF/logging.properties
@@ -0,0 +1,13 @@
+# A default java.util.logging configuration.
+# (All App Engine logging is through java.util.logging by default).
+#
+# To use this configuration, copy it into your application's WEB-INF
+# folder and add the following to your appengine-web.xml:
+#
+#
+#
+#
+#
+
+# Set the default logging level for all loggers to WARNING
+.level = WARNING
diff --git a/appengine-java21/ee8/bigtable/src/main/webapp/WEB-INF/web.xml b/appengine-java21/ee8/bigtable/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 00000000000..2fecfb4115c
--- /dev/null
+++ b/appengine-java21/ee8/bigtable/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+ bigtable.jsp
+
+
+ BIGTABLE_PROJECT
+ @bigtable.projectID@
+
+
+ BIGTABLE_INSTANCE
+ @bigtable.instanceID@
+
+
+
diff --git a/appengine-java21/ee8/bigtable/src/main/webapp/bigtable.jsp b/appengine-java21/ee8/bigtable/src/main/webapp/bigtable.jsp
new file mode 100644
index 00000000000..88a040051e6
--- /dev/null
+++ b/appengine-java21/ee8/bigtable/src/main/webapp/bigtable.jsp
@@ -0,0 +1,32 @@
+
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+<%@ page import="com.example.bigtable.BigtableHelloWorld" %>
+
+<%--
+ ~ Copyright 2017 Google LLC
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License"); you
+ ~ may not use this file except in compliance with the License. You may
+ ~ obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ ~ implied. See the License for the specific language governing
+ ~ permissions and limitations under the License.
+ --%>
+
+
+
+
+ Hello App Engine Standard using Java 21
+
+
+
Hello App Engine -- Standard for Java 21!
+
+
This is <%= BigtableHelloWorld.doHelloWorld() %>.
+
+
+
diff --git a/appengine-java21/ee8/bigtable/src/main/webapp/favicon.ico b/appengine-java21/ee8/bigtable/src/main/webapp/favicon.ico
new file mode 100644
index 00000000000..0062ab413e7
Binary files /dev/null and b/appengine-java21/ee8/bigtable/src/main/webapp/favicon.ico differ
diff --git a/appengine-java21/ee8/bigtable/src/test/java/com/example/bigtable/BigtableHelloWorldTests.java b/appengine-java21/ee8/bigtable/src/test/java/com/example/bigtable/BigtableHelloWorldTests.java
new file mode 100644
index 00000000000..7b13aa67e2f
--- /dev/null
+++ b/appengine-java21/ee8/bigtable/src/test/java/com/example/bigtable/BigtableHelloWorldTests.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.bigtable;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link BigtableHelloWorld}. */
+@RunWith(JUnit4.class)
+public class BigtableHelloWorldTests {
+
+ private BigtableHelper helper;
+
+ @Before
+ public void setUp() throws Exception {
+ helper = new BigtableHelper();
+ helper.contextInitialized(null);
+ }
+
+ @Test
+ public void bigtable_test() {
+ String result = BigtableHelloWorld.doHelloWorld();
+ assertThat(result).contains("Write some greetings to the table");
+ assertThat(result).contains("Get a single greeting by row key");
+ assertThat(result).contains("greeting0= Hello World!");
+ assertThat(result).contains("Hello Cloud Bigtable!");
+ assertThat(result).contains("Hello HBase!");
+ }
+}
diff --git a/appengine-java21/ee8/datastore/README.md b/appengine-java21/ee8/datastore/README.md
new file mode 100644
index 00000000000..cb258be8741
--- /dev/null
+++ b/appengine-java21/ee8/datastore/README.md
@@ -0,0 +1,32 @@
+# Google Cloud Datastore Sample
+
+
+
+
+This sample demonstrates how to use [Google Cloud Datastore][java-datastore]
+from [Google App Engine standard environment][ae-docs].
+
+[java-datastore]: https://cloud.google.com/appengine/docs/java/datastore/
+[ae-docs]: https://cloud.google.com/appengine/docs/java/
+
+
+## Running locally
+
+This example uses the
+[Cloud SDK Maven plugin](https://cloud.google.com/appengine/docs/legacy/standard/java/using-maven).
+To run this sample locally:
+
+ $ mvn appengine:run
+
+To see the results of the sample application, open
+[localhost:8080](http://localhost:8080) in a web browser.
+
+
+## Deploying
+
+In the following command, replace YOUR-PROJECT-ID with your
+[Google Cloud Project ID](https://developers.google.com/console/help/new/#projectnumber)
+and SOME-VERSION with a valid version number.
+
+ $ mvn clean package appengine:deploy
+ $ mvn appengine:deployIndex
diff --git a/appengine-java21/ee8/datastore/pom.xml b/appengine-java21/ee8/datastore/pom.xml
new file mode 100644
index 00000000000..ae955be85df
--- /dev/null
+++ b/appengine-java21/ee8/datastore/pom.xml
@@ -0,0 +1,181 @@
+
+
+ 4.0.0
+ war
+ 1.0-SNAPSHOT
+ com.example.appengine
+ appengine-datastore-j21
+
+
+
+ com.google.cloud.samples
+ shared-configuration
+ 1.2.0
+
+
+
+ 21
+ 21
+
+
+
+
+
+ libraries-bom
+ com.google.cloud
+ import
+ pom
+ 26.28.0
+
+
+
+
+
+
+ com.google.appengine
+ appengine-api-1.0-sdk
+ 2.0.39
+
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ 4.0.4
+ jar
+ provided
+
+
+
+ taglibs
+ standard
+ 1.1.2
+
+
+ jakarta.servlet.jsp.jstl
+ jakarta.servlet.jsp.jstl-api
+ 1.2.7
+
+
+
+ com.google.auto.value
+ auto-value
+ 1.11.0
+ provided
+
+
+
+ com.google.auto.value
+ auto-value-annotations
+
+
+
+ com.google.code.findbugs
+ jsr305
+ 3.0.2
+
+
+
+ com.google.guava
+ guava
+
+
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+ org.mockito
+ mockito-core
+ 4.11.0
+ test
+
+
+
+ com.google.appengine
+ appengine-testing
+ 2.0.39
+ test
+
+
+ com.google.appengine
+ appengine-api-stubs
+ 2.0.39
+ test
+
+
+ com.google.appengine
+ appengine-tools-sdk
+ 2.0.39
+ test
+
+
+ com.google.truth
+ truth
+ 1.4.4
+ test
+
+
+
+
+
+ ${project.build.directory}/${project.build.finalName}/WEB-INF/classes
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+ 3.4.0
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.13
+
+
+ com.google.cloud.tools
+ appengine-maven-plugin
+ 2.5.0
+
+ GCLOUD_CONFIG
+ GCLOUD_CONFIG
+ true
+ true
+
+
+
+
+ maven-compiler-plugin
+ 3.11.0
+
+
+
+ com.google.auto.value
+ auto-value
+ 1.11.0
+
+
+
+
+
+
+
diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/AbstractGuestbook.java b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/AbstractGuestbook.java
new file mode 100644
index 00000000000..a7b46bc4727
--- /dev/null
+++ b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/AbstractGuestbook.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2016 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine;
+
+import com.example.time.Clock;
+import com.google.appengine.api.datastore.DatastoreService;
+import com.google.appengine.api.datastore.DatastoreServiceFactory;
+import com.google.appengine.api.datastore.Entity;
+import com.google.appengine.api.users.User;
+import com.google.appengine.api.users.UserService;
+import com.google.appengine.api.users.UserServiceFactory;
+import com.google.common.collect.ImmutableList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * A log of notes left by users.
+ *
+ *
This is meant to be subclassed to demonstrate different storage structures in Datastore.
+ */
+abstract class AbstractGuestbook {
+
+ private final DatastoreService datastore;
+ private final UserService userService;
+ private final Clock clock;
+
+ AbstractGuestbook(Clock clock) {
+ this.datastore = DatastoreServiceFactory.getDatastoreService();
+ this.userService = UserServiceFactory.getUserService();
+ this.clock = clock;
+ }
+
+ /**
+ * Appends a new greeting to the guestbook and returns the {@link Entity} that was created.
+ **/
+ public Greeting appendGreeting(String content) {
+ return Greeting.create(
+ createGreeting(datastore, userService.getCurrentUser(), Date.from(clock.now()), content));
+ }
+
+ /**
+ * Write a greeting to Datastore.
+ */
+ protected abstract Entity createGreeting(
+ DatastoreService datastore, User user, Date date, String content);
+
+ /**
+ * Return a list of the most recent greetings.
+ */
+ public List listGreetings() {
+ ImmutableList.Builder greetings = ImmutableList.builder();
+ for (Entity entity : listGreetingEntities(datastore)) {
+ greetings.add(Greeting.create(entity));
+ }
+ return greetings.build();
+ }
+
+ /**
+ * Return a list of the most recent greetings.
+ */
+ protected abstract List listGreetingEntities(DatastoreService datastore);
+}
diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/AbstractGuestbookServlet.java b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/AbstractGuestbookServlet.java
new file mode 100644
index 00000000000..d9a0d518b92
--- /dev/null
+++ b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/AbstractGuestbookServlet.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2016 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine;
+
+import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+abstract class AbstractGuestbookServlet extends HttpServlet {
+
+ private final AbstractGuestbook guestbook;
+
+ public AbstractGuestbookServlet(AbstractGuestbook guestbook) {
+ this.guestbook = guestbook;
+ }
+
+ private void renderGuestbook(HttpServletRequest req, HttpServletResponse resp)
+ throws IOException, ServletException {
+ resp.setContentType("text/html");
+ resp.setCharacterEncoding("UTF-8");
+ req.setAttribute("greetings", guestbook.listGreetings());
+ req.getRequestDispatcher("/guestbook.jsp").forward(req, resp);
+ }
+
+ @Override
+ public void doGet(HttpServletRequest req, HttpServletResponse resp)
+ throws IOException, ServletException {
+ renderGuestbook(req, resp);
+ }
+
+ @Override
+ public void doPost(HttpServletRequest req, HttpServletResponse resp)
+ throws IOException, ServletException {
+ String content = req.getParameter("content");
+ if (content == null || content.isEmpty()) {
+ resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "missing content");
+ return;
+ }
+ guestbook.appendGreeting(content);
+ renderGuestbook(req, resp);
+ }
+}
diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/Greeting.java b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/Greeting.java
new file mode 100644
index 00000000000..ff45e508205
--- /dev/null
+++ b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/Greeting.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2016 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine;
+
+import com.google.appengine.api.datastore.Entity;
+import com.google.appengine.api.users.User;
+import com.google.auto.value.AutoValue;
+import java.time.Instant;
+import java.util.Date;
+import javax.annotation.Nullable;
+
+@AutoValue
+public abstract class Greeting {
+
+ static Greeting create(Entity entity) {
+ User user = (User) entity.getProperty("user");
+ Instant date = ((Date) entity.getProperty("date")).toInstant();
+ String content = (String) entity.getProperty("content");
+ return new AutoValue_Greeting(user, date, content);
+ }
+
+ @Nullable
+ public abstract User getUser();
+
+ public abstract Instant getDate();
+
+ public abstract String getContent();
+}
diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/Guestbook.java b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/Guestbook.java
new file mode 100644
index 00000000000..ebacbf540ae
--- /dev/null
+++ b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/Guestbook.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2016 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine;
+
+import com.example.time.Clock;
+import com.google.appengine.api.datastore.DatastoreService;
+import com.google.appengine.api.datastore.Entity;
+import com.google.appengine.api.datastore.FetchOptions;
+import com.google.appengine.api.datastore.Query;
+import com.google.appengine.api.users.User;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * A log of notes left by users.
+ *
+ *
This demonstrates the use of Google Cloud Datastore using the App Engine APIs. See the documentation for more
+ * information.
+ */
+class Guestbook extends AbstractGuestbook {
+
+ Guestbook(Clock clock) {
+ super(clock);
+ }
+
+ @Override
+ protected Entity createGreeting(
+ DatastoreService datastore, User user, Date date, String content) {
+ // No parent key specified, so Greeting is a root entity.
+ Entity greeting = new Entity("Greeting");
+ greeting.setProperty("user", user);
+ greeting.setProperty("date", date);
+ greeting.setProperty("content", content);
+
+ datastore.put(greeting);
+ return greeting;
+ }
+
+ @Override
+ protected List listGreetingEntities(DatastoreService datastore) {
+ Query query = new Query("Greeting").addSort("date", Query.SortDirection.DESCENDING);
+ return datastore.prepare(query).asList(FetchOptions.Builder.withLimit(10));
+ }
+}
diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/GuestbookServlet.java b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/GuestbookServlet.java
new file mode 100644
index 00000000000..019e5a4210c
--- /dev/null
+++ b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/GuestbookServlet.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine;
+
+import com.example.time.SystemClock;
+
+public class GuestbookServlet extends AbstractGuestbookServlet {
+
+ public GuestbookServlet() {
+ super(new Guestbook(new SystemClock()));
+ }
+}
diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/GuestbookStrong.java b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/GuestbookStrong.java
new file mode 100644
index 00000000000..a3c1bc08743
--- /dev/null
+++ b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/GuestbookStrong.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2016 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine;
+
+import com.example.time.Clock;
+import com.google.appengine.api.datastore.DatastoreService;
+import com.google.appengine.api.datastore.Entity;
+import com.google.appengine.api.datastore.FetchOptions;
+import com.google.appengine.api.datastore.Key;
+import com.google.appengine.api.datastore.KeyFactory;
+import com.google.appengine.api.datastore.Query;
+import com.google.appengine.api.users.User;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * A log of notes left by users.
+ *
+ *
This demonstrates the use of Google Cloud Datastore using the App Engine APIs. See the documentation for more
+ * information.
+ */
+class GuestbookStrong extends AbstractGuestbook {
+
+ private final String guestbookName;
+
+ GuestbookStrong(String guestbookName, Clock clock) {
+ super(clock);
+ this.guestbookName = guestbookName;
+ }
+
+ @Override
+ protected Entity createGreeting(
+ DatastoreService datastore, User user, Date date, String content) {
+ // String guestbookName = "my guestbook"; -- Set elsewhere (injected to the constructor).
+ Key guestbookKey = KeyFactory.createKey("Guestbook", guestbookName);
+
+ // Place greeting in the same entity group as guestbook.
+ Entity greeting = new Entity("Greeting", guestbookKey);
+ greeting.setProperty("user", user);
+ greeting.setProperty("date", date);
+ greeting.setProperty("content", content);
+
+ datastore.put(greeting);
+ return greeting;
+ }
+
+ @Override
+ protected List listGreetingEntities(DatastoreService datastore) {
+ Key guestbookKey = KeyFactory.createKey("Guestbook", guestbookName);
+ Query query =
+ new Query("Greeting", guestbookKey)
+ .setAncestor(guestbookKey)
+ .addSort("date", Query.SortDirection.DESCENDING);
+ return datastore.prepare(query).asList(FetchOptions.Builder.withLimit(10));
+ }
+}
diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/GuestbookStrongServlet.java b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/GuestbookStrongServlet.java
new file mode 100644
index 00000000000..2861c88d991
--- /dev/null
+++ b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/GuestbookStrongServlet.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine;
+
+import com.example.time.SystemClock;
+
+public class GuestbookStrongServlet extends AbstractGuestbookServlet {
+
+ public static final String GUESTBOOK_ID = "my guestbook";
+
+ public GuestbookStrongServlet() {
+ super(new GuestbookStrong(GUESTBOOK_ID, new SystemClock()));
+ }
+}
diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/ListPeopleServlet.java b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/ListPeopleServlet.java
new file mode 100644
index 00000000000..8bb61cbbadc
--- /dev/null
+++ b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/ListPeopleServlet.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2016 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine;
+
+// [START gae_java21_datastore_cursors]
+
+import com.google.appengine.api.datastore.Cursor;
+import com.google.appengine.api.datastore.DatastoreService;
+import com.google.appengine.api.datastore.DatastoreServiceFactory;
+import com.google.appengine.api.datastore.Entity;
+import com.google.appengine.api.datastore.FetchOptions;
+import com.google.appengine.api.datastore.PreparedQuery;
+import com.google.appengine.api.datastore.Query;
+import com.google.appengine.api.datastore.Query.SortDirection;
+import com.google.appengine.api.datastore.QueryResultList;
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class ListPeopleServlet extends HttpServlet {
+
+ static final int PAGE_SIZE = 15;
+ private final DatastoreService datastore;
+
+ public ListPeopleServlet() {
+ datastore = DatastoreServiceFactory.getDatastoreService();
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException {
+ FetchOptions fetchOptions = FetchOptions.Builder.withLimit(PAGE_SIZE);
+
+ // If this servlet is passed a cursor parameter, let's use it.
+ String startCursor = req.getParameter("cursor");
+ if (startCursor != null) {
+ fetchOptions.startCursor(Cursor.fromWebSafeString(startCursor));
+ }
+
+ Query q = new Query("Person").addSort("name", SortDirection.ASCENDING);
+ PreparedQuery pq = datastore.prepare(q);
+
+ QueryResultList results;
+ try {
+ results = pq.asQueryResultList(fetchOptions);
+ } catch (IllegalArgumentException e) {
+ // IllegalArgumentException happens when an invalid cursor is used.
+ // A user could have manually entered a bad cursor in the URL or there
+ // may have been an internal implementation detail change in App Engine.
+ // Redirect to the page without the cursor parameter to show something
+ // rather than an error.
+ resp.sendRedirect("/people");
+ return;
+ }
+
+ resp.setContentType("text/html");
+ resp.setCharacterEncoding("UTF-8");
+ PrintWriter w = resp.getWriter();
+ w.println("");
+ w.println("");
+ w.println("Cloud Datastore Cursor Sample");
+ w.println("
");
+ for (Entity entity : results) {
+ w.println("
" + entity.getProperty("name") + "
");
+ }
+ w.println("
");
+
+ String cursorString = results.getCursor().toWebSafeString();
+
+ // This servlet lives at '/people'.
+ w.println("Next page");
+ }
+}
+// [END gae_java21_datastore_cursors]
diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/ProjectionServlet.java b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/ProjectionServlet.java
new file mode 100644
index 00000000000..d062f9d6d17
--- /dev/null
+++ b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/ProjectionServlet.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2016 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine;
+
+import com.google.appengine.api.datastore.DatastoreService;
+import com.google.appengine.api.datastore.DatastoreServiceFactory;
+import com.google.appengine.api.datastore.Entity;
+import com.google.appengine.api.datastore.FetchOptions;
+import com.google.appengine.api.datastore.Key;
+import com.google.appengine.api.datastore.KeyFactory;
+import com.google.appengine.api.datastore.PropertyProjection;
+import com.google.appengine.api.datastore.Query;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Date;
+import java.util.List;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Servlet to demonstrate use of Datastore projection queries.
+ *
+ *
See the
+ * documentation
+ * for using Datastore projection queries from the Google App Engine standard environment.
+ */
+@SuppressWarnings("serial")
+public class ProjectionServlet extends HttpServlet {
+
+ private static final String GUESTBOOK_ID = GuestbookStrongServlet.GUESTBOOK_ID;
+ private final DatastoreService datastore;
+
+ public ProjectionServlet() {
+ datastore = DatastoreServiceFactory.getDatastoreService();
+ }
+
+ @Override
+ public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ resp.setContentType("text/plain");
+ resp.setCharacterEncoding("UTF-8");
+ PrintWriter out = resp.getWriter();
+ out.printf("Latest entries from guestbook: \n");
+
+ Key guestbookKey = KeyFactory.createKey("Guestbook", GUESTBOOK_ID);
+ Query query = new Query("Greeting", guestbookKey);
+ addGuestbookProjections(query);
+ printGuestbookEntries(datastore, query, out);
+ }
+
+ private void addGuestbookProjections(Query query) {
+ query.addProjection(new PropertyProjection("content", String.class));
+ query.addProjection(new PropertyProjection("date", Date.class));
+ }
+
+ private void printGuestbookEntries(DatastoreService datastore, Query query, PrintWriter out) {
+ List guests = datastore.prepare(query).asList(FetchOptions.Builder.withLimit(5));
+ for (Entity guest : guests) {
+ String content = (String) guest.getProperty("content");
+ Date stamp = (Date) guest.getProperty("date");
+ out.printf("Message %s posted on %s.\n", content, stamp.toString());
+ }
+ }
+}
diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/StartupServlet.java b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/StartupServlet.java
new file mode 100644
index 00000000000..9f4d2d06970
--- /dev/null
+++ b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/StartupServlet.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2016 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine;
+
+import com.google.appengine.api.datastore.DatastoreService;
+import com.google.appengine.api.datastore.DatastoreServiceFactory;
+import com.google.appengine.api.datastore.Entity;
+import com.google.appengine.api.datastore.EntityNotFoundException;
+import com.google.appengine.api.datastore.Key;
+import com.google.appengine.api.datastore.KeyFactory;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * A startup handler to populate the datastore with example entities.
+ */
+public class StartupServlet extends HttpServlet {
+
+ static final String IS_POPULATED_ENTITY = "IsPopulated";
+ static final String IS_POPULATED_KEY_NAME = "is-populated";
+
+ private static final String PERSON_ENTITY = "Person";
+ private static final String NAME_PROPERTY = "name";
+ private static final ImmutableList US_PRESIDENTS =
+ ImmutableList.builder()
+ .add("George Washington")
+ .add("John Adams")
+ .add("Thomas Jefferson")
+ .add("James Madison")
+ .add("James Monroe")
+ .add("John Quincy Adams")
+ .add("Andrew Jackson")
+ .add("Martin Van Buren")
+ .add("William Henry Harrison")
+ .add("John Tyler")
+ .add("James K. Polk")
+ .add("Zachary Taylor")
+ .add("Millard Fillmore")
+ .add("Franklin Pierce")
+ .add("James Buchanan")
+ .add("Abraham Lincoln")
+ .add("Andrew Johnson")
+ .add("Ulysses S. Grant")
+ .add("Rutherford B. Hayes")
+ .add("James A. Garfield")
+ .add("Chester A. Arthur")
+ .add("Grover Cleveland")
+ .add("Benjamin Harrison")
+ .add("Grover Cleveland")
+ .add("William McKinley")
+ .add("Theodore Roosevelt")
+ .add("William Howard Taft")
+ .add("Woodrow Wilson")
+ .add("Warren G. Harding")
+ .add("Calvin Coolidge")
+ .add("Herbert Hoover")
+ .add("Franklin D. Roosevelt")
+ .add("Harry S. Truman")
+ .add("Dwight D. Eisenhower")
+ .add("John F. Kennedy")
+ .add("Lyndon B. Johnson")
+ .add("Richard Nixon")
+ .add("Gerald Ford")
+ .add("Jimmy Carter")
+ .add("Ronald Reagan")
+ .add("George H. W. Bush")
+ .add("Bill Clinton")
+ .add("George W. Bush")
+ .add("Barack Obama")
+ .build();
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException {
+ resp.setContentType("text/plain");
+ DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
+
+ Key isPopulatedKey = KeyFactory.createKey(IS_POPULATED_ENTITY, IS_POPULATED_KEY_NAME);
+ boolean isAlreadyPopulated;
+ try {
+ datastore.get(isPopulatedKey);
+ isAlreadyPopulated = true;
+ } catch (EntityNotFoundException expected) {
+ isAlreadyPopulated = false;
+ }
+ if (isAlreadyPopulated) {
+ resp.getWriter().println("ok");
+ return;
+ }
+
+ ImmutableList.Builder people = ImmutableList.builder();
+ for (String name : US_PRESIDENTS) {
+ Entity person = new Entity(PERSON_ENTITY);
+ person.setProperty(NAME_PROPERTY, name);
+ people.add(person);
+ }
+ datastore.put(people.build());
+ datastore.put(new Entity(isPopulatedKey));
+ resp.getWriter().println("ok");
+ }
+}
diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/StatsServlet.java b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/StatsServlet.java
new file mode 100644
index 00000000000..74531a56e56
--- /dev/null
+++ b/appengine-java21/ee8/datastore/src/main/java/com/example/appengine/StatsServlet.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine;
+
+import com.google.appengine.api.datastore.DatastoreService;
+import com.google.appengine.api.datastore.DatastoreServiceFactory;
+import com.google.appengine.api.datastore.Entity;
+import com.google.appengine.api.datastore.Query;
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class StatsServlet extends HttpServlet {
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException {
+ // [START gae_java21_datastore_stat_example]
+ DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
+ Entity globalStat = datastore.prepare(new Query("__Stat_Total__")).asSingleEntity();
+ Long totalBytes = (Long) globalStat.getProperty("bytes");
+ Long totalEntities = (Long) globalStat.getProperty("count");
+ // [END gae_java21_datastore_stat_example]
+
+ resp.setContentType("text/plain");
+ resp.setCharacterEncoding("UTF-8");
+ PrintWriter w = resp.getWriter();
+ w.printf("%d bytes\n%d entities\n", totalBytes, totalEntities);
+ }
+}
diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/time/Clock.java b/appengine-java21/ee8/datastore/src/main/java/com/example/time/Clock.java
new file mode 100644
index 00000000000..a703577d7f9
--- /dev/null
+++ b/appengine-java21/ee8/datastore/src/main/java/com/example/time/Clock.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.time;
+
+import java.time.Instant;
+
+/**
+ * Provides the current value of "now." To preserve testability, avoid all other libraries that
+ * access the system clock (whether {@linkplain System#currentTimeMillis directly} or {@linkplain
+ * java.time.Instant#now() indirectly}).
+ *
+ *
In production, use the {@link SystemClock} implementation to return the "real" system time. In
+ * tests, either use {@link com.example.time.testing.FakeClock}, or get an instance from a mocking
+ * framework such as Mockito.
+ */
+public interface Clock {
+
+ /**
+ * Returns the current, absolute time according to this clock.
+ */
+ Instant now();
+}
diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/time/SystemClock.java b/appengine-java21/ee8/datastore/src/main/java/com/example/time/SystemClock.java
new file mode 100644
index 00000000000..dcde41330f6
--- /dev/null
+++ b/appengine-java21/ee8/datastore/src/main/java/com/example/time/SystemClock.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.time;
+
+import java.time.Instant;
+
+/**
+ * Clock implementation that returns the "real" system time.
+ *
+ *
This class exists so that we can use a fake implementation for unit testing classes that need
+ * the current time value. See {@link Clock} for general information about clocks.
+ */
+public class SystemClock implements Clock {
+
+ /**
+ * Creates a new instance. All {@code SystemClock} instances function identically.
+ */
+ public SystemClock() {
+ }
+
+ @Override
+ public Instant now() {
+ return Instant.now();
+ }
+}
diff --git a/appengine-java21/ee8/datastore/src/main/java/com/example/time/testing/FakeClock.java b/appengine-java21/ee8/datastore/src/main/java/com/example/time/testing/FakeClock.java
new file mode 100644
index 00000000000..a08343e51b6
--- /dev/null
+++ b/appengine-java21/ee8/datastore/src/main/java/com/example/time/testing/FakeClock.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2016 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.time.testing;
+
+import com.example.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * A Clock that returns a fixed Instant value as the current clock time. The fixed Instant is
+ * settable for testing. Test code should hold a reference to the FakeClock, while code under test
+ * should hold a Clock reference.
+ *
+ *
The clock time can be incremented/decremented manually, with {@link #incrementTime} and {@link
+ * #decrementTime} respectively.
+ *
+ *
The clock can also be configured so that the time is incremented whenever {@link #now()} is
+ * called: see {@link #setAutoIncrementStep}.
+ */
+public class FakeClock implements Clock {
+
+ private static final Instant DEFAULT_TIME = Instant.ofEpochMilli(1000000000L);
+ private final long baseTimeMs;
+ private final AtomicLong fakeNowMs;
+ private volatile long autoIncrementStepMs;
+
+ /**
+ * Creates a FakeClock instance initialized to an arbitrary constant.
+ */
+ public FakeClock() {
+ this(DEFAULT_TIME);
+ }
+
+ /**
+ * Creates a FakeClock instance initialized to the given time.
+ */
+ public FakeClock(Instant now) {
+ baseTimeMs = now.toEpochMilli();
+ fakeNowMs = new AtomicLong(baseTimeMs);
+ }
+
+ /**
+ * Sets the value of the underlying instance for testing purposes.
+ *
+ * @return this
+ */
+ public FakeClock setNow(Instant now) {
+ fakeNowMs.set(now.toEpochMilli());
+ return this;
+ }
+
+ @Override
+ public Instant now() {
+ return getAndAdd(autoIncrementStepMs);
+ }
+
+ /**
+ * Returns the current time without applying an auto increment, if configured. The default
+ * behavior of {@link #now()} is the same as this method.
+ */
+ public Instant peek() {
+ return Instant.ofEpochMilli(fakeNowMs.get());
+ }
+
+ /**
+ * Reset the given clock back to the base time with which the FakeClock was initially
+ * constructed.
+ *
+ * @return this
+ */
+ public FakeClock resetTime() {
+ fakeNowMs.set(baseTimeMs);
+ return this;
+ }
+
+ /**
+ * Increments the clock time by the given duration.
+ *
+ * @param duration the duration to increment the clock time by
+ * @return this
+ */
+ public FakeClock incrementTime(Duration duration) {
+ incrementTime(duration.toMillis());
+ return this;
+ }
+
+ /**
+ * Increments the clock time by the given duration.
+ *
+ * @param durationMs the duration to increment the clock time by, in milliseconds
+ * @return this
+ */
+ public FakeClock incrementTime(long durationMs) {
+ fakeNowMs.addAndGet(durationMs);
+ return this;
+ }
+
+ /**
+ * Decrements the clock time by the given duration.
+ *
+ * @param duration the duration to decrement the clock time by
+ * @return this
+ */
+ public FakeClock decrementTime(Duration duration) {
+ incrementTime(-duration.toMillis());
+ return this;
+ }
+
+ /**
+ * Decrements the clock time by the given duration.
+ *
+ * @param durationMs the duration to decrement the clock time by, in milliseconds
+ * @return this
+ */
+ public FakeClock decrementTime(long durationMs) {
+ incrementTime(-durationMs);
+ return this;
+ }
+
+ /**
+ * Sets the increment applied to the clock whenever it is queried. The increment is zero by
+ * default: the clock is left unchanged when queried.
+ *
+ * @param autoIncrementStep the new auto increment duration
+ * @return this
+ */
+ public FakeClock setAutoIncrementStep(Duration autoIncrementStep) {
+ setAutoIncrementStep(autoIncrementStep.toMillis());
+ return this;
+ }
+
+ /**
+ * Sets the increment applied to the clock whenever it is queried. The increment is zero by
+ * default: the clock is left unchanged when queried.
+ *
+ * @param autoIncrementStepMs the new auto increment duration, in milliseconds
+ * @return this
+ */
+ public FakeClock setAutoIncrementStep(long autoIncrementStepMs) {
+ this.autoIncrementStepMs = autoIncrementStepMs;
+ return this;
+ }
+
+ /**
+ * Atomically adds the given value to the current time.
+ *
+ * @param durationMs the duration to add, in milliseconds
+ * @return the updated current time
+ * @see AtomicLong#addAndGet
+ */
+ protected final Instant addAndGet(long durationMs) {
+ return Instant.ofEpochMilli(fakeNowMs.addAndGet(durationMs));
+ }
+
+ /**
+ * Atomically adds the given value to the current time.
+ *
+ * @param durationMs the duration to add, in milliseconds
+ * @return the previous time
+ * @see AtomicLong#getAndAdd
+ */
+ protected final Instant getAndAdd(long durationMs) {
+ return Instant.ofEpochMilli(fakeNowMs.getAndAdd(durationMs));
+ }
+}
diff --git a/appengine-java21/ee8/datastore/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java21/ee8/datastore/src/main/webapp/WEB-INF/appengine-web.xml
new file mode 100644
index 00000000000..56e91137870
--- /dev/null
+++ b/appengine-java21/ee8/datastore/src/main/webapp/WEB-INF/appengine-web.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ java21
+ true
+
+
+
+
diff --git a/appengine-java21/ee8/datastore/src/main/webapp/WEB-INF/datastore-indexes.xml b/appengine-java21/ee8/datastore/src/main/webapp/WEB-INF/datastore-indexes.xml
new file mode 100644
index 00000000000..c99175eba3b
--- /dev/null
+++ b/appengine-java21/ee8/datastore/src/main/webapp/WEB-INF/datastore-indexes.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/appengine-java21/ee8/datastore/src/main/webapp/WEB-INF/web.xml b/appengine-java21/ee8/datastore/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 00000000000..dddc47141c7
--- /dev/null
+++ b/appengine-java21/ee8/datastore/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,98 @@
+
+
+
+
+ guestbook-strong
+ com.example.appengine.GuestbookStrongServlet
+
+
+ guestbook-strong
+ /
+
+
+ guestbook
+ com.example.appengine.GuestbookServlet
+
+
+ guestbook
+ /guestbook
+
+
+ people
+ com.example.appengine.ListPeopleServlet
+
+
+ people
+ /people
+
+
+ projection
+ com.example.appengine.ProjectionServlet
+
+
+ projection
+ /projection
+
+
+ stats
+ com.example.appengine.StatsServlet
+
+
+ stats
+ /stats
+
+
+
+
+ startup
+ com.example.appengine.StartupServlet
+
+
+ startup
+ /_ah/start
+
+
+
+
+ profile
+ /*
+
+
+ CONFIDENTIAL
+
+
+ *
+
+
+
+
+
+ profile
+ /stats
+
+
+ CONFIDENTIAL
+
+
+ admin
+
+
+
diff --git a/appengine-java21/ee8/datastore/src/main/webapp/guestbook.jsp b/appengine-java21/ee8/datastore/src/main/webapp/guestbook.jsp
new file mode 100644
index 00000000000..5d2d5708f7e
--- /dev/null
+++ b/appengine-java21/ee8/datastore/src/main/webapp/guestbook.jsp
@@ -0,0 +1,45 @@
+
+
+<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
+<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
+
+
");
+ }
+ }
+}
+// [END gae_java21_users_api]
diff --git a/appengine-java21/ee8/users/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java21/ee8/users/src/main/webapp/WEB-INF/appengine-web.xml
new file mode 100644
index 00000000000..71f00b07474
--- /dev/null
+++ b/appengine-java21/ee8/users/src/main/webapp/WEB-INF/appengine-web.xml
@@ -0,0 +1,8 @@
+
+
+ java21
+
+
+
+ true
+
diff --git a/appengine-java21/ee8/users/src/main/webapp/WEB-INF/web.xml b/appengine-java21/ee8/users/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 00000000000..5fece3ce81b
--- /dev/null
+++ b/appengine-java21/ee8/users/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,26 @@
+
+
+
+
+ userapi
+
+ true
+
diff --git a/appengine-java21/ee8/users/src/test/java/com/example/appengine/users/UsersServletTest.java b/appengine-java21/ee8/users/src/test/java/com/example/appengine/users/UsersServletTest.java
new file mode 100644
index 00000000000..e7195d3f2f2
--- /dev/null
+++ b/appengine-java21/ee8/users/src/test/java/com/example/appengine/users/UsersServletTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2015 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.users;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+import static org.mockito.Mockito.when;
+
+import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import javax.management.remote.JMXPrincipal;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link UsersServlet}.
+ */
+@RunWith(JUnit4.class)
+public class UsersServletTest {
+
+ private static final String FAKE_URL = "fakey.fake.fak";
+ private static final String FAKE_NAME = "Fake";
+ // Set up a helper so that the ApiProxy returns a valid environment for local testing.
+ private final LocalServiceTestHelper helper = new LocalServiceTestHelper();
+
+ @Mock
+ private HttpServletRequest mockRequestNotLoggedIn;
+ @Mock
+ private HttpServletRequest mockRequestLoggedIn;
+ @Mock
+ private HttpServletResponse mockResponse;
+ private StringWriter responseWriter;
+ private UsersServlet servletUnderTest;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.openMocks(this);
+ helper.setUp();
+
+ // Set up some fake HTTP requests
+ // If the user isn't logged in, use this request
+ when(mockRequestNotLoggedIn.getRequestURI()).thenReturn(FAKE_URL);
+ when(mockRequestNotLoggedIn.getUserPrincipal()).thenReturn(null);
+
+ // If the user is logged in, use this request
+ when(mockRequestLoggedIn.getRequestURI()).thenReturn(FAKE_URL);
+ // Most of the classes that implement Principal have been
+ // deprecated. JMXPrincipal seems like a safe choice.
+ when(mockRequestLoggedIn.getUserPrincipal()).thenReturn(new JMXPrincipal(FAKE_NAME));
+
+ // Set up a fake HTTP response.
+ responseWriter = new StringWriter();
+ when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter));
+
+ servletUnderTest = new UsersServlet();
+ }
+
+ @After
+ public void tearDown() {
+ helper.tearDown();
+ }
+
+ @Test
+ public void doGet_userNotLoggedIn_writesResponse() throws Exception {
+ servletUnderTest.doGet(mockRequestNotLoggedIn, mockResponse);
+
+ // If a user isn't logged in, we expect a prompt
+ // to login to be returned.
+ assertWithMessage("UsersServlet response")
+ .that(responseWriter.toString())
+ .contains("
");
+ }
+
+ @Test
+ public void doGet_userLoggedIn_writesResponse() throws Exception {
+ servletUnderTest.doGet(mockRequestLoggedIn, mockResponse);
+
+ // If a user is logged in, we expect a prompt
+ // to logout to be returned.
+ assertWithMessage("UsersServlet response")
+ .that(responseWriter.toString())
+ .contains("
We add a @Parent to tell the object about its ancestor. We are doing this to support many
- * guestbooks. Objectify, unlike the AppEngine library requires that you specify the fields you want
- * to index using @Index. Only indexing the fields you need can lead to substantial gains in
- * performance -- though if not indexing your data from the start will require indexing it later.
- *
- *
NOTE - all the properties are PUBLIC so that can keep the code simple.
- */
-@Entity
-public class Greeting {
-
- @Parent
- Key theBook;
- @Id
- public Long id;
-
- public String authorEmail;
- public String authorId;
- public String content;
- @Index
- public Date date;
-
- /**
- * Simple constructor just sets the date.
- */
- public Greeting() {
- date = new Date();
- }
-
- /**
- * A convenience constructor.
- */
- public Greeting(String book, String content) {
- this();
- if (book != null) {
- theBook = Key.create(Guestbook.class, book); // Creating the Ancestor key
- } else {
- theBook = Key.create(Guestbook.class, "default");
- }
- this.content = content;
- }
-
- /**
- * Construct a Greeting with all params.
- *
- * @param book .
- * @param content .
- * @param id .
- * @param email .
- */
- public Greeting(String book, String content, String id, String email) {
- this(book, content);
- authorEmail = email;
- authorId = id;
- }
-}
-//[END all]
diff --git a/appengine-java8/multitenancy/src/main/java/com/example/appengine/Guestbook.java b/appengine-java8/multitenancy/src/main/java/com/example/appengine/Guestbook.java
deleted file mode 100644
index 40840fa7a21..00000000000
--- a/appengine-java8/multitenancy/src/main/java/com/example/appengine/Guestbook.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2015 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.
- */
-
-//[START all]
-
-package com.example.appengine;
-
-import com.googlecode.objectify.annotation.Entity;
-import com.googlecode.objectify.annotation.Id;
-
-/**
- * The @Entity tells Objectify about our entity. We also register it in OfyHelper.java -- very
- * important.
- *
- *
This is never actually created, but gives a hint to Objectify about our Ancestor key.
- */
-@Entity
-public class Guestbook {
-
- @Id
- public String book;
-}
-//[END all]
diff --git a/appengine-java8/multitenancy/src/main/java/com/example/appengine/MultitenancyServlet.java b/appengine-java8/multitenancy/src/main/java/com/example/appengine/MultitenancyServlet.java
deleted file mode 100644
index 8cdc4d7a470..00000000000
--- a/appengine-java8/multitenancy/src/main/java/com/example/appengine/MultitenancyServlet.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * 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.appengine;
-
-import com.google.appengine.api.NamespaceManager;
-import com.google.appengine.api.memcache.MemcacheService;
-import com.google.appengine.api.memcache.MemcacheServiceFactory;
-import com.google.appengine.api.search.Index;
-import com.google.appengine.api.search.IndexSpec;
-import com.google.appengine.api.search.SearchService;
-import com.google.appengine.api.search.SearchServiceConfig;
-import com.google.appengine.api.search.SearchServiceFactory;
-import com.google.appengine.api.users.UserServiceFactory;
-import java.io.IOException;
-import java.io.PrintWriter;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-// [START example]
-@SuppressWarnings("serial")
-public class MultitenancyServlet extends HttpServlet {
-
- @Override
- public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
- String namespace;
-
- PrintWriter out = resp.getWriter();
- out.println("Code Snippets -- not yet fully runnable as an app");
-
- // [START temp_namespace]
- // Set the namepace temporarily to "abc"
- String oldNamespace = NamespaceManager.get();
- NamespaceManager.set("abc");
- try {
- // ... perform operation using current namespace ...
- } finally {
- NamespaceManager.set(oldNamespace);
- }
- // [END temp_namespace]
-
- // [START per_user_namespace]
- if (com.google.appengine.api.NamespaceManager.get() == null) {
- // Assuming there is a logged in user.
- namespace = UserServiceFactory.getUserService().getCurrentUser().getUserId();
- NamespaceManager.set(namespace);
- }
- // [END per_user_namespace]
- String value = "something here";
-
- // [START ns_memcache]
- // Create a MemcacheService that uses the current namespace by
- // calling NamespaceManager.get() for every access.
- MemcacheService current = MemcacheServiceFactory.getMemcacheService();
-
- // stores value in namespace "abc"
- oldNamespace = NamespaceManager.get();
- NamespaceManager.set("abc");
- try {
- current.put("key", value); // stores value in namespace “abc”
- } finally {
- NamespaceManager.set(oldNamespace);
- }
- // [END ns_memcache]
-
- // [START specific_memcache]
- // Create a MemcacheService that uses the namespace "abc".
- MemcacheService explicit = MemcacheServiceFactory.getMemcacheService("abc");
- explicit.put("key", value); // stores value in namespace "abc"
- // [END specific_memcache]
-
- //[START searchns]
- // Set the current namespace to "aSpace"
- NamespaceManager.set("aSpace");
- // Create a SearchService with the namespace "aSpace"
- SearchService searchService = SearchServiceFactory.getSearchService();
- // Create an IndexSpec
- IndexSpec indexSpec = IndexSpec.newBuilder().setName("myIndex").build();
- // Create an Index with the namespace "aSpace"
- Index index = searchService.getIndex(indexSpec);
- // [END searchns]
-
- // [START searchns_2]
- // Create a SearchServiceConfig, specifying the namespace "anotherSpace"
- SearchServiceConfig config =
- SearchServiceConfig.newBuilder().setNamespace("anotherSpace").build();
- // Create a SearchService with the namespace "anotherSpace"
- searchService = SearchServiceFactory.getSearchService(config);
- // Create an IndexSpec
- indexSpec = IndexSpec.newBuilder().setName("myindex").build();
- // Create an Index with the namespace "anotherSpace"
- index = searchService.getIndex(indexSpec);
- // [END searchns_2]
-
- }
-}
-// [END example]
diff --git a/appengine-java8/multitenancy/src/main/java/com/example/appengine/NamespaceFilter.java b/appengine-java8/multitenancy/src/main/java/com/example/appengine/NamespaceFilter.java
deleted file mode 100644
index 17e84e90620..00000000000
--- a/appengine-java8/multitenancy/src/main/java/com/example/appengine/NamespaceFilter.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.appengine;
-
-import com.google.appengine.api.NamespaceManager;
-import java.io.IOException;
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-
-// [START nsfilter]
-// Filter to set the Google Apps domain as the namespace.
-public class NamespaceFilter implements Filter {
-
- @Override
- public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
- throws IOException, ServletException {
- // Make sure set() is only called if the current namespace is not already set.
- if (NamespaceManager.get() == null) {
- // If your app is hosted on appspot, this will be empty. Otherwise it will be the domain
- // the app is hosted on.
- NamespaceManager.set(NamespaceManager.getGoogleAppsNamespace());
- }
- chain.doFilter(req, res); // Pass request back down the filter chain
- }
- // [END nsfilter]
-
- @Override
- public void init(FilterConfig filterConfig) throws ServletException {
- }
-
- @Override
- public void destroy() {
- }
-}
diff --git a/appengine-java8/multitenancy/src/main/java/com/example/appengine/OfyHelper.java b/appengine-java8/multitenancy/src/main/java/com/example/appengine/OfyHelper.java
deleted file mode 100644
index 7585fc5b454..00000000000
--- a/appengine-java8/multitenancy/src/main/java/com/example/appengine/OfyHelper.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2015 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.
- */
-
-//[START all]
-
-package com.example.appengine;
-
-import com.googlecode.objectify.ObjectifyService;
-import javax.servlet.ServletContextEvent;
-import javax.servlet.ServletContextListener;
-
-/**
- * OfyHelper, a ServletContextListener, is setup in web.xml to run before a JSP is run. This is
- * required to let JSP's access Ofy.
- */
-public class OfyHelper implements ServletContextListener {
-
- /**
- * A ServletContextListener initializer.
- *
- * @param event .
- */
- public void contextInitialized(ServletContextEvent event) {
- // This will be invoked as part of a warmup request, or the first user request if no warmup
- // request.
- ObjectifyService.init();
- ObjectifyService.register(Guestbook.class);
- ObjectifyService.register(Greeting.class);
- }
-
- public void contextDestroyed(ServletContextEvent event) {
- // App Engine does not currently invoke this method.
- }
-}
-//[END all]
diff --git a/appengine-java8/multitenancy/src/main/java/com/example/appengine/SignGuestbookServlet.java b/appengine-java8/multitenancy/src/main/java/com/example/appengine/SignGuestbookServlet.java
deleted file mode 100644
index 0e452b20d32..00000000000
--- a/appengine-java8/multitenancy/src/main/java/com/example/appengine/SignGuestbookServlet.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2015 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.
- */
-
-//[START all]
-
-package com.example.appengine;
-
-import com.google.appengine.api.users.User;
-import com.google.appengine.api.users.UserService;
-import com.google.appengine.api.users.UserServiceFactory;
-import com.googlecode.objectify.ObjectifyService;
-import java.io.IOException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-/**
- * Form Handling Servlet - most of the action for this sample is in webapp/guestbook.jsp. It
- * displays {@link Greeting}'s.
- */
-public class SignGuestbookServlet extends HttpServlet {
-
- // Process the http POST of the form
- @Override
- public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
- Greeting greeting;
-
- UserService userService = UserServiceFactory.getUserService();
- User user = userService.getCurrentUser(); // Find out who the user is.
-
- String guestbookName = req.getParameter("guestbookName");
- String content = req.getParameter("content");
- if (user != null) {
- greeting = new Greeting(guestbookName, content, user.getUserId(), user.getEmail());
- } else {
- greeting = new Greeting(guestbookName, content);
- }
-
- // Use Objectify to save the greeting and now() is used to make the call synchronously as we
- // will immediately get a new page using redirect and we want the data to be present.
- ObjectifyService.ofy().save().entity(greeting).now();
-
- resp.sendRedirect("/guestbook.jsp?guestbookName=" + guestbookName);
- }
-}
-//[END all]
diff --git a/appengine-java8/multitenancy/src/main/java/com/example/appengine/SomeRequestServlet.java b/appengine-java8/multitenancy/src/main/java/com/example/appengine/SomeRequestServlet.java
deleted file mode 100644
index 2bfa418cfd0..00000000000
--- a/appengine-java8/multitenancy/src/main/java/com/example/appengine/SomeRequestServlet.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.appengine;
-
-import com.google.appengine.api.NamespaceManager;
-import com.google.appengine.api.taskqueue.QueueFactory;
-import com.google.appengine.api.taskqueue.TaskOptions;
-import java.io.IOException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-// [START tq_3]
-public class SomeRequestServlet extends HttpServlet {
-
- // Handler for URL get requests.
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
-
- // Increment the count for the current namespace asynchronously.
- QueueFactory.getDefaultQueue()
- .add(TaskOptions.Builder.withUrl("/_ah/update_count").param("countName", "SomeRequest"));
- // Increment the global count and set the
- // namespace locally. The namespace is
- // transferred to the invoked request and
- // executed asynchronously.
- String namespace = NamespaceManager.get();
- try {
- NamespaceManager.set("-global-");
- QueueFactory.getDefaultQueue()
- .add(TaskOptions.Builder.withUrl("/_ah/update_count").param("countName", "SomeRequest"));
- } finally {
- NamespaceManager.set(namespace);
- }
- resp.setContentType("text/plain");
- resp.getWriter().println("Counts are being updated.");
- }
-}
-// [END tq_3]
diff --git a/appengine-java8/multitenancy/src/main/java/com/example/appengine/UpdateCountsServlet.java b/appengine-java8/multitenancy/src/main/java/com/example/appengine/UpdateCountsServlet.java
deleted file mode 100644
index 76e15d88990..00000000000
--- a/appengine-java8/multitenancy/src/main/java/com/example/appengine/UpdateCountsServlet.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * 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.appengine;
-
-import static com.googlecode.objectify.ObjectifyService.ofy;
-
-import com.google.appengine.api.NamespaceManager;
-import com.googlecode.objectify.annotation.Entity;
-import com.googlecode.objectify.annotation.Id;
-import com.googlecode.objectify.annotation.Index;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-// [START datastore]
-// [START tq_1]
-public class UpdateCountsServlet extends HttpServlet {
-
- private static final int NUM_RETRIES = 10;
-
- @Entity
- public class CounterPojo {
-
- @Id
- public Long id;
- @Index
- public String name;
- public Long count;
-
- public CounterPojo() {
- this.count = 0L;
- }
-
- public CounterPojo(String name) {
- this.name = name;
- this.count = 0L;
- }
-
- public void increment() {
- count++;
- }
- }
-
- /**
- * Increment the count in a Counter datastore entity.
- **/
- public long updateCount(String countName) {
-
- CounterPojo cp = ofy().load().type(CounterPojo.class).filter("name", countName).first().now();
- if (cp == null) {
- cp = new CounterPojo(countName);
- }
- cp.increment();
- ofy().save().entity(cp).now();
-
- return cp.count;
- }
- // [END tq_1]
-
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp)
- throws java.io.IOException {
-
- // Update the count for the current namespace.
- updateCount("request");
-
- // Update the count for the "-global-" namespace.
- String namespace = NamespaceManager.get();
- try {
- // "-global-" is namespace reserved by the application.
- NamespaceManager.set("-global-");
- updateCount("request");
- } finally {
- NamespaceManager.set(namespace);
- }
- resp.setContentType("text/plain");
- resp.getWriter().println("Counts are now updated.");
- }
- // [END datastore]
-
- // [START tq_2]
- // called from Task Queue
- @Override
- protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
- String[] countName = req.getParameterValues("countName");
- if (countName.length != 1) {
- resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
- return;
- }
- updateCount(countName[0]);
- }
- // [END tq_2]
-}
diff --git a/appengine-java8/multitenancy/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java8/multitenancy/src/main/webapp/WEB-INF/appengine-web.xml
deleted file mode 100644
index b85f72b69e3..00000000000
--- a/appengine-java8/multitenancy/src/main/webapp/WEB-INF/appengine-web.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
- java8
- true
-
-
-
-
-
diff --git a/appengine-java8/multitenancy/src/main/webapp/WEB-INF/logging.properties b/appengine-java8/multitenancy/src/main/webapp/WEB-INF/logging.properties
deleted file mode 100644
index a17206681f0..00000000000
--- a/appengine-java8/multitenancy/src/main/webapp/WEB-INF/logging.properties
+++ /dev/null
@@ -1,13 +0,0 @@
-# A default java.util.logging configuration.
-# (All App Engine logging is through java.util.logging by default).
-#
-# To use this configuration, copy it into your application's WEB-INF
-# folder and add the following to your appengine-web.xml:
-#
-#
-#
-#
-#
-
-# Set the default logging level for all loggers to WARNING
-.level = WARNING
diff --git a/appengine-java8/multitenancy/src/main/webapp/WEB-INF/web.xml b/appengine-java8/multitenancy/src/main/webapp/WEB-INF/web.xml
deleted file mode 100644
index c8853530ceb..00000000000
--- a/appengine-java8/multitenancy/src/main/webapp/WEB-INF/web.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-
-
-
-
-
-
- sign
- com.example.appengine.SignGuestbookServlet
-
-
-
- sign
- /sign
-
-
-
- guestbook.jsp
-
-
-
-
-
- ObjectifyFilter
- com.googlecode.objectify.ObjectifyFilter
-
-
- ObjectifyFilter
- /*
-
-
- com.example.appengine.OfyHelper
-
-
-
-
-
-
- NamespaceFilter
- com.example.appengine.NamespaceFilter
-
-
-
- NamespaceFilter
- /sign
-
-
-
-
diff --git a/appengine-java8/multitenancy/src/main/webapp/guestbook.jsp b/appengine-java8/multitenancy/src/main/webapp/guestbook.jsp
deleted file mode 100644
index 317ba765ddc..00000000000
--- a/appengine-java8/multitenancy/src/main/webapp/guestbook.jsp
+++ /dev/null
@@ -1,106 +0,0 @@
-<%-- //[START all]--%>
-<%@ page contentType="text/html;charset=UTF-8" language="java" %>
-<%@ page import="com.google.appengine.api.users.User" %>
-<%@ page import="com.google.appengine.api.users.UserService" %>
-<%@ page import="com.google.appengine.api.users.UserServiceFactory" %>
-
-<%-- //[START imports]--%>
-<%@ page import="com.example.appengine.Greeting" %>
-<%@ page import="com.example.appengine.Guestbook" %>
-<%@ page import="com.googlecode.objectify.Key" %>
-<%@ page import="com.googlecode.objectify.ObjectifyService" %>
-<%-- //[END imports]--%>
-
-<%@ page import="java.util.List" %>
-<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-
-
-
Hello, ${fn:escapeXml(user.nickname)}! (You can
- sign out.)
-<%
- } else {
-%>
-
Hello!
- Sign in
- to include your name with greetings you post.
-<%
- }
-%>
-
-<%-- //[START datastore]--%>
-<%
- // Create the correct Ancestor key
- Key theBook = Key.create(Guestbook.class, guestbookName);
-
- // Run an ancestor query to ensure we see the most up-to-date
- // view of the Greetings belonging to the selected Guestbook.
- List greetings = ObjectifyService.ofy()
- .load()
- .type(Greeting.class) // We want only Greetings
- .ancestor(theBook) // Anyone in this book
- .order("-date") // Most recent first - date is indexed.
- .limit(5) // Only show 5 of them.
- .list();
-
- if (greetings.isEmpty()) {
-%>
-
Guestbook '${fn:escapeXml(guestbookName)}' has no messages.
-<%
- } else {
-%>
-
Messages in Guestbook '${fn:escapeXml(guestbookName)}'.
-<%
- }
- }
-%>
-
-
-<%-- //[END datastore]--%>
-
-
-
-
-<%-- //[END all]--%>
diff --git a/appengine-java8/multitenancy/src/main/webapp/stylesheets/main.css b/appengine-java8/multitenancy/src/main/webapp/stylesheets/main.css
deleted file mode 100644
index 05d72d5536d..00000000000
--- a/appengine-java8/multitenancy/src/main/webapp/stylesheets/main.css
+++ /dev/null
@@ -1,4 +0,0 @@
-body {
- font-family: Verdana, Helvetica, sans-serif;
- background-color: #FFFFCC;
-}
diff --git a/appengine-java8/multitenancy/src/test/java/com/example/appengine/GreetingTest.java b/appengine-java8/multitenancy/src/test/java/com/example/appengine/GreetingTest.java
deleted file mode 100644
index 09d27914899..00000000000
--- a/appengine-java8/multitenancy/src/test/java/com/example/appengine/GreetingTest.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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.appengine;
-
-import static com.example.appengine.GuestbookTestUtilities.cleanDatastore;
-import static org.junit.Assert.assertEquals;
-
-import com.google.appengine.api.datastore.DatastoreService;
-import com.google.appengine.api.datastore.DatastoreServiceFactory;
-import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
-import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
-import com.googlecode.objectify.Key;
-import com.googlecode.objectify.ObjectifyService;
-import com.googlecode.objectify.util.Closeable;
-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 GreetingTest {
-
- private static final String TEST_CONTENT = "The world is Blue today";
-
- private final LocalServiceTestHelper helper =
- new LocalServiceTestHelper(
- // Set no eventual consistency, that way queries return all results.
- // https://cloud.google
- // .com/appengine/docs/java/tools/localunittesting
- // #Java_Writing_High_Replication_Datastore_tests
- new LocalDatastoreServiceTestConfig()
- .setDefaultHighRepJobPolicyUnappliedJobPercentage(0));
-
- private Closeable closeable;
- private DatastoreService ds;
-
- @Before
- public void setUp() throws Exception {
-
- helper.setUp();
- ds = DatastoreServiceFactory.getDatastoreService();
-
- ObjectifyService.init();
- ObjectifyService.register(Guestbook.class);
- ObjectifyService.register(Greeting.class);
-
- closeable = ObjectifyService.begin();
-
- cleanDatastore(ds, "default");
- }
-
- @After
- public void tearDown() {
- cleanDatastore(ds, "default");
- helper.tearDown();
- closeable.close();
- }
-
- @Test
- public void createSaveObject() throws Exception {
-
- Greeting g = new Greeting("default", TEST_CONTENT);
- ObjectifyService.ofy().save().entity(g).now();
-
- Greeting greeting = ObjectifyService.ofy().load().type(Greeting.class).ancestor(
- Key.create(Guestbook.class, "default")).first().now();
- assertEquals(greeting.content, TEST_CONTENT);
- }
-}
diff --git a/appengine-java8/multitenancy/src/test/java/com/example/appengine/GuestbookTestUtilities.java b/appengine-java8/multitenancy/src/test/java/com/example/appengine/GuestbookTestUtilities.java
deleted file mode 100644
index 9dcafcc102b..00000000000
--- a/appengine-java8/multitenancy/src/test/java/com/example/appengine/GuestbookTestUtilities.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.appengine;
-
-import com.google.appengine.api.datastore.DatastoreService;
-import com.google.appengine.api.datastore.Entity;
-import com.google.appengine.api.datastore.FetchOptions;
-import com.google.appengine.api.datastore.Key;
-import com.google.appengine.api.datastore.KeyFactory;
-import com.google.appengine.api.datastore.PreparedQuery;
-import com.google.appengine.api.datastore.Query;
-import java.util.ArrayList;
-import java.util.List;
-
-public class GuestbookTestUtilities {
-
- public static void cleanDatastore(DatastoreService ds, String book) {
- Query query =
- new Query("Greeting")
- .setAncestor(new KeyFactory.Builder("Guestbook", book).getKey())
- .setKeysOnly();
- PreparedQuery pq = ds.prepare(query);
- List entities = pq.asList(FetchOptions.Builder.withDefaults());
- ArrayList keys = new ArrayList<>(entities.size());
-
- for (Entity e : entities) {
- keys.add(e.getKey());
- }
- ds.delete(keys);
- }
-}
diff --git a/appengine-java8/multitenancy/src/test/java/com/example/appengine/SignGuestbookServletTest.java b/appengine-java8/multitenancy/src/test/java/com/example/appengine/SignGuestbookServletTest.java
deleted file mode 100644
index e964411adec..00000000000
--- a/appengine-java8/multitenancy/src/test/java/com/example/appengine/SignGuestbookServletTest.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright 2015 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.appengine;
-
-import static com.example.appengine.GuestbookTestUtilities.cleanDatastore;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.when;
-
-import com.google.appengine.api.datastore.DatastoreService;
-import com.google.appengine.api.datastore.DatastoreServiceFactory;
-import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
-import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
-import com.googlecode.objectify.Key;
-import com.googlecode.objectify.ObjectifyService;
-import com.googlecode.objectify.util.Closeable;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Unit tests for {@link com.example.appengine.SignGuestbookServlet}.
- */
-@RunWith(JUnit4.class)
-public class SignGuestbookServletTest {
-
- private static final String FAKE_URL = "fakey.org/sign";
-
- private final LocalServiceTestHelper helper =
- new LocalServiceTestHelper(
- // Set no eventual consistency, that way queries return all results.
- // https://cloud.google
- // .com/appengine/docs/java/tools/localunittesting
- // #Java_Writing_High_Replication_Datastore_tests
- new LocalDatastoreServiceTestConfig()
- .setDefaultHighRepJobPolicyUnappliedJobPercentage(0));
-
- private static final String testPhrase = "Noew is the time";
-
- @Mock
- private HttpServletRequest mockRequest;
-
- @Mock
- private HttpServletResponse mockResponse;
-
- private StringWriter stringWriter;
- private SignGuestbookServlet servletUnderTest;
- private Closeable closeable;
- private DatastoreService ds;
-
- @Before
- public void setUp() throws Exception {
-
- MockitoAnnotations.openMocks(this);
- helper.setUp();
- ds = DatastoreServiceFactory.getDatastoreService();
-
- // Set up some fake HTTP requests
- when(mockRequest.getRequestURI()).thenReturn(FAKE_URL);
- when(mockRequest.getParameter("guestbookName")).thenReturn("default2");
- when(mockRequest.getParameter("content")).thenReturn(testPhrase);
-
- stringWriter = new StringWriter();
- when(mockResponse.getWriter()).thenReturn(new PrintWriter(stringWriter));
-
- servletUnderTest = new SignGuestbookServlet();
-
- ObjectifyService.init();
- ObjectifyService.register(Guestbook.class);
- ObjectifyService.register(Greeting.class);
-
- closeable = ObjectifyService.begin();
-
- cleanDatastore(ds, "default");
- }
-
- @After
- public void tearDown() {
- cleanDatastore(ds, "default");
- helper.tearDown();
- closeable.close();
- }
-
- @Test
- public void doPostUserNotLoggedIn() throws Exception {
- servletUnderTest.doPost(mockRequest, mockResponse);
-
- Greeting greeting = ObjectifyService.ofy().load().type(Greeting.class)
- .ancestor(Key.create(Guestbook.class, "default2")).first().now();
- assertEquals(greeting.content, testPhrase);
- }
-}
diff --git a/asset/src/test/java/com/example/asset/RealTimeFeedIT.java b/asset/src/test/java/com/example/asset/RealTimeFeedIT.java
index fbfe2057b1c..5f528404ec2 100644
--- a/asset/src/test/java/com/example/asset/RealTimeFeedIT.java
+++ b/asset/src/test/java/com/example/asset/RealTimeFeedIT.java
@@ -32,7 +32,6 @@
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -42,7 +41,6 @@
@RunWith(JUnit4.class)
@SuppressWarnings("checkstyle:abbreviationaswordinname")
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Ignore("TODO: Fix https://github.com/GoogleCloudPlatform/java-docs-samples/issues/8963")
public class RealTimeFeedIT {
private static final String topicId = "topicId";
private static final String feedId = UUID.randomUUID().toString();
@@ -63,14 +61,16 @@ private String getProjectNumber(String projectId) {
@BeforeClass
public static void createTopic() throws Exception {
- TopicAdminClient topicAdminClient = TopicAdminClient.create();
- topicAdminClient.createTopic(topicName);
+ try (TopicAdminClient topicAdminClient = TopicAdminClient.create()) {
+ topicAdminClient.createTopic(topicName);
+ }
}
@AfterClass
public static void deleteTopic() throws Exception {
- TopicAdminClient topicAdminClient = TopicAdminClient.create();
- topicAdminClient.deleteTopic(topicName);
+ try (TopicAdminClient topicAdminClient = TopicAdminClient.create()) {
+ topicAdminClient.deleteTopic(topicName);
+ }
}
@Before
diff --git a/asset/src/test/java/com/example/asset/SearchIT.java b/asset/src/test/java/com/example/asset/SearchIT.java
index 1fa86a2b15b..0ebb2f765ce 100644
--- a/asset/src/test/java/com/example/asset/SearchIT.java
+++ b/asset/src/test/java/com/example/asset/SearchIT.java
@@ -29,7 +29,6 @@
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -67,7 +66,6 @@ public void tearDown() {
bigquery.delete(datasetId, DatasetDeleteOption.deleteContents());
}
- @Ignore("Blocked on https://github.com/GoogleCloudPlatform/java-docs-samples/issues/8798")
@Test
public void testSearchAllResourcesExample() throws Exception {
// Wait 120 seconds to let dataset creation event go to CAI
diff --git a/auth/.gitignore b/auth/.gitignore
new file mode 100644
index 00000000000..305842cb8a4
--- /dev/null
+++ b/auth/.gitignore
@@ -0,0 +1,3 @@
+# Ignore GCP and IdP secret files
+src/main/java/com/google/cloud/auth/samples/customcredentials/aws/custom-credentials-aws-secrets.json
+src/main/java/com/google/cloud/auth/samples/customcredentials/okta/custom-credentials-okta-secrets.json
diff --git a/auth/README.md b/auth/README.md
index 8ee3187d212..186970b53dd 100644
--- a/auth/README.md
+++ b/auth/README.md
@@ -35,16 +35,37 @@ You can then run a given `ClassName` via:
mvn exec:java -Dexec.mainClass=com.google.cloud.auth.samples.AuthExample
-Dexec.args="compute"
+### Analyze text sentiment using LanguageService API with API key authentication
+
+Create an API key via the [Google Cloud console:](https://developers.google.com/workspace/guides/create-credentials#api-key)
+
+Once you have an API key replace it in the main function in ApiKeyAuthExample and run the following command
+
+ mvn exec:java -Dexec.mainClass=com.google.cloud.auth.samples.ApiKeyAuthExample
+
## Downscoping with Credential Access Boundaries
The same configuration above applies.
-To run the samples for [Downscoping with Credential Access Boundaries](https://cloud.google.com/iam/docs/downscoping-short-lived-credentials)
-you must provide both a bucket name and object name under the TODO(developer): in the main method of `DownscopingExample`.
+This section provides examples for [Downscoping with Credential Access Boundaries](https://cloud.google.com/iam/docs/downscoping-short-lived-credentials).
+There are two examples demonstrating different ways to implement downscoping.
+
+**`DownscopedAccessTokenGenerator` and `DownscopedAccessTokenConsumer` Examples:**
+
+These examples demonstrate a common pattern for downscoping, using a token broker and consumer.
+The `DownscopedAccessTokenGenerator` generates the downscoped access token using a client-side approach, and the `DownscopedAccessTokenConsumer` uses it to access Cloud Storage resources.
+To run the `DownscopedAccessTokenConsumer`, you must provide a bucket name and object name under the `TODO(developer):` in the `main` method.
+You can then run `DownscopedAccessTokenConsumer` via:
+
+ mvn exec:java -Dexec.mainClass=com.google.cloud.auth.samples.DownscopedAccessTokenConsumer
+
+**`DownscopingExample` Example:**
+
+This example demonstrates downscoping using a server-side approach. To run this example you must provide both a bucket name and object name under the TODO(developer): in the main method of `DownscopingExample`.
You can then run `DownscopingExample` via:
- mvn exec:exec
+ mvn exec:java -Dexec.mainClass=com.google.cloud.auth.samples.DownscopingExample
## Tests
Run all tests:
diff --git a/auth/pom.xml b/auth/pom.xml
index bb9be513846..c943b0a1ca4 100644
--- a/auth/pom.xml
+++ b/auth/pom.xml
@@ -30,7 +30,7 @@ limitations under the License.
com.google.cloud.samplesshared-configuration
- 1.2.0
+ 1.2.2
@@ -48,7 +48,14 @@ limitations under the License.
com.google.cloudlibraries-bom
- 26.32.0
+ 26.49.0
+ pom
+ import
+
+
+ software.amazon.awssdk
+ bom
+ 2.25.41pomimport
@@ -67,11 +74,29 @@ limitations under the License.
com.google.authgoogle-auth-library-oauth2-http
+ 1.32.0
+
+
+ com.google.auth
+ google-auth-library-cab-token-generator
+ 1.32.0com.google.cloudgoogle-cloud-apikeys
+
+ com.google.cloud
+ google-cloud-language
+
+
+ software.amazon.awssdk
+ auth
+
+
+ software.amazon.awssdk
+ regions
+ junit
@@ -104,11 +129,6 @@ limitations under the License.
java
-
- -classpath
-
- com.google.cloud.auth.samples.DownscopingExample
-
diff --git a/auth/src/main/java/UndeleteApiKey.java b/auth/src/main/java/UndeleteApiKey.java
new file mode 100644
index 00000000000..cd509c705b3
--- /dev/null
+++ b/auth/src/main/java/UndeleteApiKey.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// [START apikeys_undelete_api_key]
+import com.google.api.apikeys.v2.ApiKeysClient;
+import com.google.api.apikeys.v2.Key;
+import com.google.api.apikeys.v2.UndeleteKeyRequest;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class UndeleteApiKey {
+
+ public static void main(String[] args)
+ throws IOException, ExecutionException, InterruptedException, TimeoutException {
+ // TODO(developer): Replace these variables before running the sample.
+ // Project ID or project number of the Google Cloud project.
+ String projectId = "YOUR_PROJECT_ID";
+ // The API key id to undelete.
+ String keyId = "YOUR_KEY_ID";
+
+ undeleteApiKey(projectId, keyId);
+ }
+
+ // Undeletes an API key.
+ public static void undeleteApiKey(String projectId, String keyId)
+ 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.
+ try (ApiKeysClient apiKeysClient = ApiKeysClient.create()) {
+
+ // Initialize the undelete request and set the argument.
+ UndeleteKeyRequest undeleteKeyRequest = UndeleteKeyRequest.newBuilder()
+ .setName(String.format("projects/%s/locations/global/keys/%s", projectId, keyId))
+ .build();
+
+ // Make the request and wait for the operation to complete.
+ Key undeletedKey = apiKeysClient.undeleteKeyAsync(undeleteKeyRequest)
+ .get(3, TimeUnit.MINUTES);
+
+ System.out.printf("Successfully undeleted the API key: %s", undeletedKey.getName());
+ }
+ }
+}
+// [END apikeys_undelete_api_key]
\ No newline at end of file
diff --git a/auth/src/main/java/com/google/cloud/auth/samples/ApiKeyAuthExample.java b/auth/src/main/java/com/google/cloud/auth/samples/ApiKeyAuthExample.java
new file mode 100644
index 00000000000..7975abad40e
--- /dev/null
+++ b/auth/src/main/java/com/google/cloud/auth/samples/ApiKeyAuthExample.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.auth.samples;
+
+// [START auth_cloud_api_key]
+import com.google.cloud.language.v2.AnalyzeSentimentResponse;
+import com.google.cloud.language.v2.Document;
+import com.google.cloud.language.v2.LanguageServiceClient;
+import com.google.cloud.language.v2.LanguageServiceSettings;
+import java.io.IOException;
+
+// [END auth_cloud_api_key]
+
+/**
+ * Demonstrate how to authenticate requests using an API Key using the Language API as an example.
+ */
+public class ApiKeyAuthExample {
+
+ // [START auth_cloud_api_key]
+ static String authenticateUsingApiKey(String apiKey) throws IOException {
+ LanguageServiceSettings settings =
+ LanguageServiceSettings.newBuilder().setApiKey(apiKey).build();
+ try (LanguageServiceClient client = LanguageServiceClient.create(settings)) {
+ Document document =
+ Document.newBuilder()
+ .setContent("Hello World!")
+ .setType(Document.Type.PLAIN_TEXT)
+ .build();
+
+ AnalyzeSentimentResponse actualResponse = client.analyzeSentiment(document);
+
+ return actualResponse.getDocumentSentiment().toString();
+ }
+ }
+ // [END auth_cloud_api_key]
+
+ public static void main(String[] args) throws IOException {
+ // TODO(Developer): Before running this sample, replace the variable(s) below.
+ // API key created in developer's project.
+ String apiKey = "api-key";
+
+ authenticateUsingApiKey(apiKey);
+ }
+}
diff --git a/auth/src/main/java/com/google/cloud/auth/samples/DownscopedAccessTokenConsumer.java b/auth/src/main/java/com/google/cloud/auth/samples/DownscopedAccessTokenConsumer.java
new file mode 100644
index 00000000000..e59f5028f18
--- /dev/null
+++ b/auth/src/main/java/com/google/cloud/auth/samples/DownscopedAccessTokenConsumer.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.auth.samples;
+
+// [START auth_client_cab_consumer]
+import com.google.auth.oauth2.AccessToken;
+import com.google.auth.oauth2.OAuth2CredentialsWithRefresh;
+import com.google.cloud.storage.Blob;
+import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.StorageOptions;
+import java.io.IOException;
+// [END auth_client_cab_consumer]
+
+
+/**
+ * Demonstrates retrieving a Cloud Storage blob using a downscoped. This example showcases the
+ * consumer side of the downscoping process. It retrieves a blob's content using credentials that
+ * have limited access based on a pre-defined Credential Access Boundary.
+ */
+public class DownscopedAccessTokenConsumer {
+
+ public static void main(String[] args) throws IOException {
+ // TODO(developer): Replace these variables before running the sample.
+ // The Cloud Storage bucket name.
+ String bucketName = "your-gcs-bucket-name";
+ // The Cloud Storage object name that resides in the specified bucket.
+ String objectName = "your-gcs-object-name";
+
+ retrieveBlobWithDownscopedToken(bucketName, objectName);
+ }
+
+ /**
+ * Simulates token consumer readonly access to the specified object.
+ *
+ * @param bucketName The name of the Cloud Storage bucket containing the blob.
+ * @param objectName The name of the Cloud Storage object (blob).
+ * @return The content of the blob as a String, or {@code null} if the blob does not exist.
+ * @throws IOException If an error occurs during communication with Cloud Storage or token
+ * retrieval. This can include issues with authentication, authorization, or network
+ * connectivity.
+ */
+ // [START auth_client_cab_consumer]
+ public static String retrieveBlobWithDownscopedToken(
+ final String bucketName, final String objectName) throws IOException {
+ // You can pass an `OAuth2RefreshHandler` to `OAuth2CredentialsWithRefresh` which will allow the
+ // library to seamlessly handle downscoped token refreshes on expiration.
+ OAuth2CredentialsWithRefresh.OAuth2RefreshHandler handler =
+ new OAuth2CredentialsWithRefresh.OAuth2RefreshHandler() {
+ @Override
+ public AccessToken refreshAccessToken() throws IOException {
+ // The common pattern of usage is to have a token broker pass the downscoped short-lived
+ // access tokens to a token consumer via some secure authenticated channel.
+ // For illustration purposes, we are generating the downscoped token locally.
+ // We want to test the ability to limit access to objects with a certain prefix string
+ // in the resource bucket. objectName.substring(0, 3) is the prefix here. This field is
+ // not required if access to all bucket resources are allowed. If access to limited
+ // resources in the bucket is needed, this mechanism can be used.
+ return DownscopedAccessTokenGenerator
+ .getTokenFromBroker(bucketName, objectName);
+ }
+ };
+
+ AccessToken downscopedToken = handler.refreshAccessToken();
+
+ OAuth2CredentialsWithRefresh credentials =
+ OAuth2CredentialsWithRefresh.newBuilder()
+ .setAccessToken(downscopedToken)
+ .setRefreshHandler(handler)
+ .build();
+
+ StorageOptions options = StorageOptions.newBuilder().setCredentials(credentials).build();
+ Storage storage = options.getService();
+
+ Blob blob = storage.get(bucketName, objectName);
+ if (blob == null) {
+ return null;
+ }
+ return new String(blob.getContent());
+ }
+ // [END auth_client_cab_consumer]
+}
diff --git a/auth/src/main/java/com/google/cloud/auth/samples/DownscopedAccessTokenGenerator.java b/auth/src/main/java/com/google/cloud/auth/samples/DownscopedAccessTokenGenerator.java
new file mode 100644
index 00000000000..3564bb6b3d3
--- /dev/null
+++ b/auth/src/main/java/com/google/cloud/auth/samples/DownscopedAccessTokenGenerator.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.auth.samples;
+
+// [START auth_client_cab_token_broker]
+import com.google.auth.credentialaccessboundary.ClientSideCredentialAccessBoundaryFactory;
+import com.google.auth.oauth2.AccessToken;
+import com.google.auth.oauth2.CredentialAccessBoundary;
+import com.google.auth.oauth2.GoogleCredentials;
+import dev.cel.common.CelValidationException;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+// [END auth_client_cab_token_broker]
+
+/**
+ * Demonstrates how to use ClientSideCredentialAccessBoundaryFactory to generate downscoped tokens.
+ */
+public class DownscopedAccessTokenGenerator {
+
+ /**
+ * Simulates a token broker generating downscoped tokens for specific objects in a bucket.
+ *
+ * @param bucketName The name of the Cloud Storage bucket.
+ * @param objectPrefix Prefix of the object name for downscoped token access.
+ * @return An AccessToken representing the downscoped token.
+ * @throws IOException If an error occurs during token generation.
+ */
+ // [START auth_client_cab_token_broker]
+ public static AccessToken getTokenFromBroker(String bucketName, String objectPrefix)
+ throws IOException {
+ // Retrieve the source credentials from ADC.
+ GoogleCredentials sourceCredentials =
+ GoogleCredentials.getApplicationDefault()
+ .createScoped("https://www.googleapis.com/auth/cloud-platform");
+
+ // Initialize the Credential Access Boundary rules.
+ String availableResource = "//storage.googleapis.com/projects/_/buckets/" + bucketName;
+
+ // Downscoped credentials will have readonly access to the resource.
+ String availablePermission = "inRole:roles/storage.objectViewer";
+
+ // Only objects starting with the specified prefix string in the object name will be allowed
+ // read access.
+ String expression =
+ "resource.name.startsWith('projects/_/buckets/"
+ + bucketName
+ + "/objects/"
+ + objectPrefix
+ + "')";
+
+ // Build the AvailabilityCondition.
+ CredentialAccessBoundary.AccessBoundaryRule.AvailabilityCondition availabilityCondition =
+ CredentialAccessBoundary.AccessBoundaryRule.AvailabilityCondition.newBuilder()
+ .setExpression(expression)
+ .build();
+
+ // Define the single access boundary rule using the above properties.
+ CredentialAccessBoundary.AccessBoundaryRule rule =
+ CredentialAccessBoundary.AccessBoundaryRule.newBuilder()
+ .setAvailableResource(availableResource)
+ .addAvailablePermission(availablePermission)
+ .setAvailabilityCondition(availabilityCondition)
+ .build();
+
+ // Define the Credential Access Boundary with all the relevant rules.
+ CredentialAccessBoundary credentialAccessBoundary =
+ CredentialAccessBoundary.newBuilder().addRule(rule).build();
+
+ // Create an instance of ClientSideCredentialAccessBoundaryFactory.
+ ClientSideCredentialAccessBoundaryFactory factory =
+ ClientSideCredentialAccessBoundaryFactory.newBuilder()
+ .setSourceCredential(sourceCredentials)
+ .build();
+
+ // Generate the token and pass it to the Token Consumer.
+ try {
+ return factory.generateToken(credentialAccessBoundary);
+ } catch (GeneralSecurityException | CelValidationException e) {
+ throw new IOException("Error generating downscoped token", e);
+ }
+ }
+ // [END auth_client_cab_token_broker]
+}
diff --git a/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/CustomCredentialSupplierAwsWorkload.java b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/CustomCredentialSupplierAwsWorkload.java
new file mode 100644
index 00000000000..82f4836da58
--- /dev/null
+++ b/auth/src/main/java/com/google/cloud/auth/samples/customcredentials/aws/CustomCredentialSupplierAwsWorkload.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.auth.samples.customcredentials.aws;
+
+// [START auth_custom_credential_supplier_aws]
+import com.google.auth.oauth2.AwsCredentials;
+import com.google.auth.oauth2.AwsSecurityCredentials;
+import com.google.auth.oauth2.AwsSecurityCredentialsSupplier;
+import com.google.auth.oauth2.ExternalAccountSupplierContext;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.cloud.storage.Bucket;
+import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.StorageOptions;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import java.io.IOException;
+import java.io.Reader;
+import java.lang.reflect.Type;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Map;
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
+import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain;
+
+// [END auth_custom_credential_supplier_aws]
+
+/**
+ * This sample demonstrates how to use a custom AWS security credentials supplier to authenticate to
+ * Google Cloud Storage using AWS Workload Identity Federation.
+ */
+public class CustomCredentialSupplierAwsWorkload {
+
+ public static void main(String[] args) throws IOException {
+
+ // Reads the custom-credentials-aws-secrets.json if running locally.
+ loadConfigFromFile();
+
+ // The audience for the workload identity federation.
+ // Format: //iam.googleapis.com/projects//locations/global/
+ // workloadIdentityPools//providers/
+ String gcpWorkloadAudience = getConfiguration("GCP_WORKLOAD_AUDIENCE");
+
+ // The bucket to fetch data from.
+ String gcsBucketName = getConfiguration("GCS_BUCKET_NAME");
+
+ // (Optional) The service account impersonation URL.
+ String saImpersonationUrl = getConfiguration("GCP_SERVICE_ACCOUNT_IMPERSONATION_URL");
+
+ if (gcpWorkloadAudience == null || gcsBucketName == null) {
+ System.err.println(
+ "Required configuration missing. Please provide it in a "
+ + "custom-credentials-aws-secrets.json file or as environment variables: "
+ + "GCP_WORKLOAD_AUDIENCE, GCS_BUCKET_NAME");
+ return;
+ }
+
+ try {
+ System.out.println("Retrieving metadata for bucket: " + gcsBucketName + "...");
+ Bucket bucket =
+ authenticateWithAwsCredentials(gcpWorkloadAudience, saImpersonationUrl, gcsBucketName);
+
+ System.out.println(" --- SUCCESS! ---");
+ System.out.println("Bucket details:");
+ System.out.printf(" Name: %s%n", bucket.getName());
+ System.out.printf(" Location: %s%n", bucket.getLocation());
+ System.out.printf(" Storage Class: %s%n", bucket.getStorageClass());
+ System.out.printf(" Metageneration: %s%n", bucket.getMetageneration());
+ } catch (Exception e) {
+ System.err.println("Authentication or Request failed: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Helper method to retrieve configuration. It checks Environment variables first, then System
+ * properties (populated by loadConfigFromFile).
+ */
+ static String getConfiguration(String key) {
+ String value = System.getenv(key);
+ if (value == null) {
+ value = System.getProperty(key);
+ }
+ return value;
+ }
+
+ /**
+ * If a local secrets file is present, load it into the System Properties. This is a
+ * "just-in-time" configuration for local development. These variables are only set for the
+ * current process.
+ */
+ static void loadConfigFromFile() {
+ // By default, this expects the file to be in the project root.
+ String secretsFilePath = "custom-credentials-aws-secrets.json";
+ if (!Files.exists(Paths.get(secretsFilePath))) {
+ return;
+ }
+
+ try (Reader reader = Files.newBufferedReader(Paths.get(secretsFilePath))) {
+ // Use Gson to parse the JSON file into a Map
+ Gson gson = new Gson();
+ Type type = new TypeToken