From 1b5b961f31bd6b340241a0e56a644db16fcda668 Mon Sep 17 00:00:00 2001 From: Jisha Abubaker Date: Thu, 15 Jun 2017 12:36:13 -0700 Subject: [PATCH 1/3] adding java8 spanner example --- appengine-java8/pom.xml | 8 +- appengine-java8/spanner/README.md | 43 ++ appengine-java8/spanner/pom.xml | 82 ++++ .../appengine/spanner/SpannerClient.java | 136 ++++++ .../appengine/spanner/SpannerTasks.java | 436 ++++++++++++++++++ .../spanner/SpannerTasksServlet.java | 74 +++ .../src/main/webapp/WEB-INF/appengine-web.xml | 28 ++ 7 files changed, 805 insertions(+), 2 deletions(-) create mode 100644 appengine-java8/spanner/README.md create mode 100644 appengine-java8/spanner/pom.xml create mode 100644 appengine-java8/spanner/src/main/java/com/example/appengine/spanner/SpannerClient.java create mode 100644 appengine-java8/spanner/src/main/java/com/example/appengine/spanner/SpannerTasks.java create mode 100644 appengine-java8/spanner/src/main/java/com/example/appengine/spanner/SpannerTasksServlet.java create mode 100644 appengine-java8/spanner/src/main/webapp/WEB-INF/appengine-web.xml diff --git a/appengine-java8/pom.xml b/appengine-java8/pom.xml index 1337c87abd4..f1759dc398d 100644 --- a/appengine-java8/pom.xml +++ b/appengine-java8/pom.xml @@ -64,12 +64,16 @@ multitenancy oauth2 requests - search - sendgrid remote-client remote-server + search + + sendgrid + + spanner + static-files taskqueues-deferred diff --git a/appengine-java8/spanner/README.md b/appengine-java8/spanner/README.md new file mode 100644 index 00000000000..d3261ed5cfe --- /dev/null +++ b/appengine-java8/spanner/README.md @@ -0,0 +1,43 @@ +# Google Cloud Spanner Sample + +This sample demonstrates how to use [Google Cloud Spanner][spanner-docs] +from [Google App Engine standard environment][ae-docs]. + +[spanner-docs]: https://cloud.google.com/spanner/docs/ +[ae-docs]: https://cloud.google.com/appengine/docs/java/ + + +## Setup +- Install the [Google Cloud SDK](https://cloud.google.com/sdk/) and run: +``` + gcloud init +``` +If this is your first time creating an App engine application: +``` + gcloud app create +``` +- [Create a Spanner instance](https://cloud.google.com/spanner/docs/quickstart-console#create_an_instance). + +- Update system properties in `[appengine-web.xml](src/main/webapp/WEB-INF/appengine-web.xml): + - Required : `SPANNER_INSTANCE` + - Optional : `SPANNER_DATABASE`, + A database will created using the `SPANNER_DATABASE` name if provided, else will be auto-generated. + +## Endpoints +- `/run` : will run sample operations against the spanner instance in order. Individual tasks can be run +using the `task` query parameter. See [SpannerTasks](src/main/java/com/example/appengine/spanner/SpannerTasks.java) +for supported set of tasks. +Note : by default all the spanner example operations run in order, this operation may take a while to return. + +## Running locally +There are known IAM permission issues running this sample locally. + +## Deploying + + $ mvn clean appengine:deploy + +To see the results of the deployed sample application, open +`https://spanner-dot-PROJECTID.appspot.com/run` in a web browser. + +Note : by default all the spanner example operations run in order, this operation may take a while to return. + diff --git a/appengine-java8/spanner/pom.xml b/appengine-java8/spanner/pom.xml new file mode 100644 index 00000000000..c3c327a6369 --- /dev/null +++ b/appengine-java8/spanner/pom.xml @@ -0,0 +1,82 @@ + + + + com.example.appengine + 4.0.0 + appengine-spanner + 1.0-SNAPSHOT + war + + false + + + + appengine-java8-samples + com.google.cloud + 1.0.0 + .. + + + + + com.google.cloud + google-cloud-spanner + 0.19.0-beta + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + com.google.appengine + appengine-api-1.0-sdk + 1.9.53 + + + + + + ${project.build.directory}/${project.build.finalName}/WEB-INF/classes + + + + + org.apache.maven.plugins + 3.5.1 + maven-compiler-plugin + + 1.8 + 1.8 + + + + + com.google.cloud.tools + appengine-maven-plugin + 1.3.1 + + true + true + + + + + diff --git a/appengine-java8/spanner/src/main/java/com/example/appengine/spanner/SpannerClient.java b/appengine-java8/spanner/src/main/java/com/example/appengine/spanner/SpannerClient.java new file mode 100644 index 00000000000..e86a192e8ac --- /dev/null +++ b/appengine-java8/spanner/src/main/java/com/example/appengine/spanner/SpannerClient.java @@ -0,0 +1,136 @@ +/** + * Copyright 2017 Google Inc. + * + *

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + *

http://www.apache.org/licenses/LICENSE-2.0 + * + *

Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.appengine.spanner; + +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.DatabaseClient; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; +import java.io.IOException; +import java.util.UUID; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.annotation.WebListener; + +@WebListener +public class SpannerClient implements ServletContextListener { + + private static String PROJECT_ID; + private static String INSTANCE_ID; + private static String DATABASE_ID; + + // The initial connection can be 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 Spanner spanner = null; + private static DatabaseAdminClient databaseAdminClient = null; + private static DatabaseClient databaseClient = null; + + private static ServletContext sc; + + private static void connect() throws IOException { + if (INSTANCE_ID == null) { + if (sc != null) { + sc.log("environment variable SPANNER_INSTANCE need to be defined."); + } + return; + } + SpannerOptions options = SpannerOptions.newBuilder().build(); + PROJECT_ID = options.getProjectId(); + spanner = options.getService(); + databaseAdminClient = spanner.getDatabaseAdminClient(); + } + + static DatabaseAdminClient getDatabaseAdminClient() { + if (databaseAdminClient == null) { + try { + connect(); + } catch (IOException e) { + if (sc != null) { + sc.log("getDatabaseAdminClient ", e); + } + } + } + if (databaseAdminClient == null) { + if (sc != null) { + sc.log("Spanner : Unable to connect"); + } + } + return databaseAdminClient; + } + + static DatabaseClient getDatabaseClient() { + if (databaseClient == null) { + databaseClient = + spanner.getDatabaseClient(DatabaseId.of(PROJECT_ID, INSTANCE_ID, DATABASE_ID)); + } + return databaseClient; + } + + @Override + public void contextInitialized(ServletContextEvent event) { + if (event != null) { + sc = event.getServletContext(); + if (INSTANCE_ID == null) { + INSTANCE_ID = sc.getInitParameter("SPANNER_INSTANCE"); + } + if (DATABASE_ID == null) { + DATABASE_ID = sc.getInitParameter("SPANNER_DATABASE"); + } + } + //try system properties + if (INSTANCE_ID == null) { + INSTANCE_ID = System.getProperty("SPANNER_INSTANCE"); + } + if (DATABASE_ID == null) { + DATABASE_ID = System.getProperty("SPANNER_DATABASE"); + } + + if (DATABASE_ID == null) { + DATABASE_ID = "db-" + UUID.randomUUID().toString().substring(0, 25); + } + + try { + connect(); + } catch (IOException e) { + if (sc != null) { + sc.log("SpannerConnection - connect ", e); + } + } + if (databaseAdminClient == null) { + if (sc != null) { + sc.log("SpannerConnection - No Connection"); + } + } + if (sc != null) { + sc.log("ctx Initialized: " + INSTANCE_ID + " " + DATABASE_ID); + } + } + + @Override + public void contextDestroyed(ServletContextEvent servletContextEvent) { + // App Engine does not currently invoke this method. + databaseAdminClient = null; + } + + public static String getInstanceId() { + return INSTANCE_ID; + } + + public static String getDatabaseId() { + return DATABASE_ID; + } +} diff --git a/appengine-java8/spanner/src/main/java/com/example/appengine/spanner/SpannerTasks.java b/appengine-java8/spanner/src/main/java/com/example/appengine/spanner/SpannerTasks.java new file mode 100644 index 00000000000..d88938b6e11 --- /dev/null +++ b/appengine-java8/spanner/src/main/java/com/example/appengine/spanner/SpannerTasks.java @@ -0,0 +1,436 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine.spanner; + +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseClient; +import com.google.cloud.spanner.Key; +import com.google.cloud.spanner.KeySet; +import com.google.cloud.spanner.Mutation; +import com.google.cloud.spanner.Operation; +import com.google.cloud.spanner.ReadOnlyTransaction; +import com.google.cloud.spanner.ResultSet; +import com.google.cloud.spanner.Statement; +import com.google.cloud.spanner.Struct; +import com.google.common.base.Stopwatch; +import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +class SpannerTasks { + + enum Task { + createDatabase, + writeExampleData, + query, + read, + addMarketingBudget, + updateMarketingBudget, + queryMarketingBudget, + addIndex, + readUsingIndex, + queryUsingIndex, + addStoringIndex, + readStoringIndex, + writeTransaction, + readOnlyTransaction + } + + /** Class to contain singer sample data. */ + static class Singer { + + final long singerId; + final String firstName; + final String lastName; + + Singer(long singerId, String firstName, String lastName) { + this.singerId = singerId; + this.firstName = firstName; + this.lastName = lastName; + } + } + + /** Class to contain album sample data. */ + static class Album { + + final long singerId; + final long albumId; + final String albumTitle; + + Album(long singerId, long albumId, String albumTitle) { + this.singerId = singerId; + this.albumId = albumId; + this.albumTitle = albumTitle; + } + } + + private static final List SINGERS = + Arrays.asList( + new Singer(1, "Marc", "Richards"), + new Singer(2, "Catalina", "Smith"), + new Singer(3, "Alice", "Trentor"), + new Singer(4, "Lea", "Martin"), + new Singer(5, "David", "Lomond")); + + private static final List ALBUMS = + Arrays.asList( + new Album(1, 1, "Total Junk"), + new Album(1, 2, "Go, Go, Go"), + new Album(2, 1, "Green"), + new Album(2, 2, "Forever Hold Your Peace"), + new Album(2, 3, "Terrified")); + + private static DatabaseClient databaseClient = null; + + private static void createDatabase(PrintWriter pw) { + Iterable statements = + Arrays.asList( + "CREATE TABLE Singers (\n" + + " SingerId INT64 NOT NULL,\n" + + " FirstName STRING(1024),\n" + + " LastName STRING(1024),\n" + + " SingerInfo BYTES(MAX)\n" + + ") PRIMARY KEY (SingerId)", + "CREATE TABLE Albums (\n" + + " SingerId INT64 NOT NULL,\n" + + " AlbumId INT64 NOT NULL,\n" + + " AlbumTitle STRING(MAX)\n" + + ") PRIMARY KEY (SingerId, AlbumId),\n" + + " INTERLEAVE IN PARENT Singers ON DELETE CASCADE"); + Database db = + SpannerClient.getDatabaseAdminClient() + .createDatabase( + SpannerClient.getInstanceId(), SpannerClient.getDatabaseId(), statements) + .waitFor() + .getResult(); + pw.println("Created database [" + db.getId() + "]"); + } + + private static void writeExampleData(PrintWriter pw) { + List mutations = new ArrayList<>(); + for (Singer singer : SINGERS) { + mutations.add( + Mutation.newInsertBuilder("Singers") + .set("SingerId") + .to(singer.singerId) + .set("FirstName") + .to(singer.firstName) + .set("LastName") + .to(singer.lastName) + .build()); + } + for (Album album : ALBUMS) { + mutations.add( + Mutation.newInsertBuilder("Albums") + .set("SingerId") + .to(album.singerId) + .set("AlbumId") + .to(album.albumId) + .set("AlbumTitle") + .to(album.albumTitle) + .build()); + } + SpannerClient.getDatabaseClient().write(mutations); + } + + private static void query(PrintWriter pw) { + // singleUse() can be used to execute a single read or query against Cloud Spanner. + ResultSet resultSet = + SpannerClient.getDatabaseClient() + .singleUse() + .executeQuery(Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums")); + while (resultSet.next()) { + pw.printf("%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2)); + } + } + + private static void read(PrintWriter pw) { + ResultSet resultSet = + SpannerClient.getDatabaseClient() + .singleUse() + .read( + "Albums", + // KeySet.all() can be used to read all rows in a table. KeySet exposes other + // methods to read only a subset of the table. + KeySet.all(), + Arrays.asList("SingerId", "AlbumId", "AlbumTitle")); + while (resultSet.next()) { + pw.printf("%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2)); + } + } + + private static void addMarketingBudgetColumnToAlbums(PrintWriter pw) { + Operation op = + SpannerClient.getDatabaseAdminClient() + .updateDatabaseDdl( + SpannerClient.getInstanceId(), + SpannerClient.getDatabaseId(), + Arrays.asList("ALTER TABLE Albums ADD COLUMN MarketingBudget INT64"), + null); + op.waitFor(); + } + + // Before executing this method, a new column MarketingBudget has to be added to the Albums + // table by applying the DDL statement "ALTER TABLE Albums ADD COLUMN MarketingBudget INT64". + private static void updateMarketingBudgetData() { + // Mutation can be used to update/insert/delete a single row in a table. Here we use + // newUpdateBuilder to create update mutations. + List mutations = + Arrays.asList( + Mutation.newUpdateBuilder("Albums") + .set("SingerId") + .to(1) + .set("AlbumId") + .to(1) + .set("MarketingBudget") + .to(100000) + .build(), + Mutation.newUpdateBuilder("Albums") + .set("SingerId") + .to(2) + .set("AlbumId") + .to(2) + .set("MarketingBudget") + .to(500000) + .build()); + // This writes all the mutations to Cloud Spanner atomically. + SpannerClient.getDatabaseClient().write(mutations); + } + + private static void writeWithTransaction() { + SpannerClient.getDatabaseClient() + .readWriteTransaction() + .run( + (transactionContext -> { + // Transfer marketing budget from one album to another. We do it in a transaction to + // ensure that the transfer is atomic. + Struct row = + transactionContext.readRow( + "Albums", Key.of(2, 2), Arrays.asList("MarketingBudget")); + long album2Budget = row.getLong(0); + // Transaction will only be committed if this condition still holds at the time of + // commit. Otherwise it will be aborted and the callable will be rerun by the + // client library. + if (album2Budget >= 300000) { + long album1Budget = + transactionContext + .readRow("Albums", Key.of(1, 1), Arrays.asList("MarketingBudget")) + .getLong(0); + long transfer = 200000; + album1Budget += transfer; + album2Budget -= transfer; + transactionContext.buffer( + Mutation.newUpdateBuilder("Albums") + .set("SingerId") + .to(1) + .set("AlbumId") + .to(1) + .set("MarketingBudget") + .to(album1Budget) + .build()); + transactionContext.buffer( + Mutation.newUpdateBuilder("Albums") + .set("SingerId") + .to(2) + .set("AlbumId") + .to(2) + .set("MarketingBudget") + .to(album2Budget) + .build()); + } + return null; + })); + } + + private static void queryMarketingBudget(PrintWriter pw) { + // Rows without an explicit value for MarketingBudget will have a MarketingBudget equal to + // null. + ResultSet resultSet = + SpannerClient.getDatabaseClient() + .singleUse() + .executeQuery(Statement.of("SELECT SingerId, AlbumId, MarketingBudget FROM Albums")); + while (resultSet.next()) { + pw.printf( + "%d %d %s\n", + resultSet.getLong("SingerId"), + resultSet.getLong("AlbumId"), + // We check that the value is non null. ResultSet getters can only be used to retrieve + // non null values. + resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget")); + } + } + + private static void addIndex() { + Operation op = + SpannerClient.getDatabaseAdminClient() + .updateDatabaseDdl( + SpannerClient.getInstanceId(), + SpannerClient.getDatabaseId(), + Arrays.asList("CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)"), + null); + op.waitFor(); + } + + // Before running this example, add the index AlbumsByAlbumTitle by applying the DDL statement + // "CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)". + private static void queryUsingIndex(PrintWriter pw) { + ResultSet resultSet = + SpannerClient.getDatabaseClient() + .singleUse() + .executeQuery( + // We use FORCE_INDEX hint to specify which index to use. For more details see + // https://cloud.google.com/spanner/docs/query-syntax#from-clause + Statement.of( + "SELECT AlbumId, AlbumTitle, MarketingBudget\n" + + "FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle}\n" + + "WHERE AlbumTitle >= 'Aardvark' AND AlbumTitle < 'Goo'")); + while (resultSet.next()) { + pw.printf( + "%d %s %s\n", + resultSet.getLong("AlbumId"), + resultSet.getString("AlbumTitle"), + resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget")); + } + } + + private static void readUsingIndex(PrintWriter pw) { + ResultSet resultSet = + SpannerClient.getDatabaseClient() + .singleUse() + .readUsingIndex( + "Albums", + "AlbumsByAlbumTitle", + KeySet.all(), + Arrays.asList("AlbumId", "AlbumTitle")); + while (resultSet.next()) { + pw.printf("%d %s\n", resultSet.getLong(0), resultSet.getString(1)); + } + } + + private static void addStoringIndex() { + Operation op = + SpannerClient.getDatabaseAdminClient() + .updateDatabaseDdl( + SpannerClient.getInstanceId(), + SpannerClient.getDatabaseId(), + Arrays.asList( + "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)"), + null); + op.waitFor(); + } + + // Before running this example, create a storing index AlbumsByAlbumTitle2 by applying the DDL + // statement "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget)". + private static void readStoringIndex(PrintWriter pw) { + // We can read MarketingBudget also from the index since it stores a copy of MarketingBudget. + ResultSet resultSet = + SpannerClient.getDatabaseClient() + .singleUse() + .readUsingIndex( + "Albums", + "AlbumsByAlbumTitle2", + KeySet.all(), + Arrays.asList("AlbumId", "AlbumTitle", "MarketingBudget")); + while (resultSet.next()) { + pw.printf( + "%d %s %s\n", + resultSet.getLong(0), + resultSet.getString(1), + resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget")); + } + } + + private static void readOnlyTransaction(PrintWriter pw) { + // ReadOnlyTransaction must be closed by calling close() on it to release resources held by it. + // We use a try-with-resource block to automatically do so. + try (ReadOnlyTransaction transaction = + SpannerClient.getDatabaseClient().readOnlyTransaction()) { + ResultSet queryResultSet = + transaction.executeQuery( + Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums")); + while (queryResultSet.next()) { + pw.printf( + "%d %d %s\n", + queryResultSet.getLong(0), queryResultSet.getLong(1), queryResultSet.getString(2)); + } + ResultSet readResultSet = + transaction.read( + "Albums", KeySet.all(), Arrays.asList("SingerId", "AlbumId", "AlbumTitle")); + while (readResultSet.next()) { + pw.printf( + "%d %d %s\n", + readResultSet.getLong(0), readResultSet.getLong(1), readResultSet.getString(2)); + } + } + } + + static void runTask(Task task, PrintWriter pw) { + Stopwatch stopwatch = Stopwatch.createStarted(); + switch (task) { + case createDatabase: + createDatabase(pw); + break; + case writeExampleData: + writeExampleData(pw); + break; + case query: + query(pw); + break; + case read: + read(pw); + break; + case addMarketingBudget: + addMarketingBudgetColumnToAlbums(pw); + break; + case updateMarketingBudget: + updateMarketingBudgetData(); + break; + case queryMarketingBudget: + queryMarketingBudget(pw); + break; + case addIndex: + addIndex(); + break; + case readUsingIndex: + readUsingIndex(pw); + break; + case queryUsingIndex: + queryUsingIndex(pw); + break; + case addStoringIndex: + addStoringIndex(); + break; + case readStoringIndex: + readStoringIndex(pw); + break; + case readOnlyTransaction: + readOnlyTransaction(pw); + break; + case writeTransaction: + writeWithTransaction(); + break; + default: + break; + } + stopwatch.stop(); + pw.println(task + " in milliseconds : " + stopwatch.elapsed(TimeUnit.MILLISECONDS)); + pw.println("===================================================================="); + } +} diff --git a/appengine-java8/spanner/src/main/java/com/example/appengine/spanner/SpannerTasksServlet.java b/appengine-java8/spanner/src/main/java/com/example/appengine/spanner/SpannerTasksServlet.java new file mode 100644 index 00000000000..cb50d53e2e9 --- /dev/null +++ b/appengine-java8/spanner/src/main/java/com/example/appengine/spanner/SpannerTasksServlet.java @@ -0,0 +1,74 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.appengine.spanner; + +import com.example.appengine.spanner.SpannerTasks.Task; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Example code for using the Cloud Spanner API. This example demonstrates all the common operations + * that can be done on Cloud Spanner. These are: + * + *

+ * + *

+ * + * Individual tasks can be run using "tasks" query parameter. {@link SpannerTasks.Task} lists + * supported tasks. All tasks are run in order if no parameter or "tasks=all" is provided. + */ +@WebServlet(value = "/run") +public class SpannerTasksServlet extends HttpServlet { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text"); + PrintWriter pw = resp.getWriter(); + try { + String tasksParam = req.getParameter("tasks"); + List tasks; + if (tasksParam == null || tasksParam.equals("all")) { + // cycle through all operations in order + tasks = Arrays.asList(Task.values()); + } else { + String[] tasksStr = tasksParam.split(","); + tasks = Arrays.stream(tasksStr).map(Task::valueOf).collect(Collectors.toList()); + } + + for (Task task : tasks) { + SpannerTasks.runTask(task, pw); + } + } catch (Exception e) { + e.printStackTrace(pw); + pw.append(e.getMessage()); + resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } +} diff --git a/appengine-java8/spanner/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java8/spanner/src/main/webapp/WEB-INF/appengine-web.xml new file mode 100644 index 00000000000..76377540fec --- /dev/null +++ b/appengine-java8/spanner/src/main/webapp/WEB-INF/appengine-web.xml @@ -0,0 +1,28 @@ + + + + + true + java8 + spanner + + 1 + + + + + + + + From dba453bdac1ca4382acd637640b5101191686bff Mon Sep 17 00:00:00 2001 From: Jisha Abubaker Date: Thu, 15 Jun 2017 12:59:08 -0700 Subject: [PATCH 2/3] adding instructions to run locally using the jetty plugin --- appengine-java8/spanner/README.md | 17 ++++++++++++++--- appengine-java8/spanner/pom.xml | 7 ++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/appengine-java8/spanner/README.md b/appengine-java8/spanner/README.md index d3261ed5cfe..e183538869d 100644 --- a/appengine-java8/spanner/README.md +++ b/appengine-java8/spanner/README.md @@ -30,7 +30,19 @@ for supported set of tasks. Note : by default all the spanner example operations run in order, this operation may take a while to return. ## Running locally -There are known IAM permission issues running this sample locally. +-Authorize the local application: +``` + gcloud auth application-default login +``` +You may also [create and use service account credentials](https://cloud.google.com/docs/authentication/getting-started#creating_the_service_account). +- To run locally, we will be using the [Maven Jetty plugin](http://www.eclipse.org/jetty/documentation/9.4.x/jetty-maven-plugin.html) +``` + mvn -DSPANNER_INSTANCE=my-spanner-instance jetty:run +``` + +To see the results of the local application, open +[http://localhost:8080/run](http://localhost:8080/run) in a web browser. +Note : by default all the spanner example operations run in order, this operation may take a while to show results. ## Deploying @@ -38,6 +50,5 @@ There are known IAM permission issues running this sample locally. To see the results of the deployed sample application, open `https://spanner-dot-PROJECTID.appspot.com/run` in a web browser. - -Note : by default all the spanner example operations run in order, this operation may take a while to return. +Note : by default all the spanner example operations run in order, this operation may take a while to show results. diff --git a/appengine-java8/spanner/pom.xml b/appengine-java8/spanner/pom.xml index c3c327a6369..bb20eb22990 100644 --- a/appengine-java8/spanner/pom.xml +++ b/appengine-java8/spanner/pom.xml @@ -19,7 +19,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> com.example.appengine 4.0.0 - appengine-spanner + appengine-spanner-j8 1.0-SNAPSHOT war @@ -58,6 +58,11 @@ + + org.eclipse.jetty + jetty-maven-plugin + 9.4.6.v20170531 + org.apache.maven.plugins 3.5.1 From 707552b5435402e4f7ec674d689a2b416a5bcda7 Mon Sep 17 00:00:00 2001 From: Jisha Abubaker Date: Thu, 15 Jun 2017 15:18:47 -0700 Subject: [PATCH 3/3] cleanup, adding comments around local deployment workaround --- appengine-java8/spanner/README.md | 14 +++++++------- appengine-java8/spanner/pom.xml | 7 ++++++- .../example/appengine/spanner/SpannerClient.java | 11 +++-------- .../appengine/spanner/SpannerTasksServlet.java | 3 ++- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/appengine-java8/spanner/README.md b/appengine-java8/spanner/README.md index e183538869d..4f62a04b072 100644 --- a/appengine-java8/spanner/README.md +++ b/appengine-java8/spanner/README.md @@ -18,24 +18,24 @@ If this is your first time creating an App engine application: ``` - [Create a Spanner instance](https://cloud.google.com/spanner/docs/quickstart-console#create_an_instance). -- Update system properties in `[appengine-web.xml](src/main/webapp/WEB-INF/appengine-web.xml): - - Required : `SPANNER_INSTANCE` - - Optional : `SPANNER_DATABASE`, - A database will created using the `SPANNER_DATABASE` name if provided, else will be auto-generated. +- Update `SPANNER_INSTANCE` value in `[appengine-web.xml](src/main/webapp/WEB-INF/appengine-web.xml). ## Endpoints -- `/run` : will run sample operations against the spanner instance in order. Individual tasks can be run +- `/spanner` : will run sample operations against the spanner instance in order. Individual tasks can be run using the `task` query parameter. See [SpannerTasks](src/main/java/com/example/appengine/spanner/SpannerTasks.java) for supported set of tasks. Note : by default all the spanner example operations run in order, this operation may take a while to return. ## Running locally --Authorize the local application: +- Authorize the local application: ``` gcloud auth application-default login ``` You may also [create and use service account credentials](https://cloud.google.com/docs/authentication/getting-started#creating_the_service_account). -- To run locally, we will be using the [Maven Jetty plugin](http://www.eclipse.org/jetty/documentation/9.4.x/jetty-maven-plugin.html) + +- App Engine Maven plugins do not work correctly for this sample for local testing. + Here is the [tracking issue](https://github.com/GoogleCloudPlatform/google-cloud-java/issues/2155). + As a workaround to run locally, this sample uses the [Maven Jetty plugin](http://www.eclipse.org/jetty/documentation/9.4.x/jetty-maven-plugin.html). ``` mvn -DSPANNER_INSTANCE=my-spanner-instance jetty:run ``` diff --git a/appengine-java8/spanner/pom.xml b/appengine-java8/spanner/pom.xml index bb20eb22990..25e842239a5 100644 --- a/appengine-java8/spanner/pom.xml +++ b/appengine-java8/spanner/pom.xml @@ -58,11 +58,17 @@ + org.eclipse.jetty jetty-maven-plugin 9.4.6.v20170531 + + org.apache.maven.plugins 3.5.1 @@ -72,7 +78,6 @@ 1.8 - com.google.cloud.tools appengine-maven-plugin diff --git a/appengine-java8/spanner/src/main/java/com/example/appengine/spanner/SpannerClient.java b/appengine-java8/spanner/src/main/java/com/example/appengine/spanner/SpannerClient.java index e86a192e8ac..a0598a70d93 100644 --- a/appengine-java8/spanner/src/main/java/com/example/appengine/spanner/SpannerClient.java +++ b/appengine-java8/spanner/src/main/java/com/example/appengine/spanner/SpannerClient.java @@ -25,6 +25,7 @@ import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; +// With @WebListener annotation the webapp/WEB-INF/web.xml is no longer required. @WebListener public class SpannerClient implements ServletContextListener { @@ -87,17 +88,11 @@ public void contextInitialized(ServletContextEvent event) { if (INSTANCE_ID == null) { INSTANCE_ID = sc.getInitParameter("SPANNER_INSTANCE"); } - if (DATABASE_ID == null) { - DATABASE_ID = sc.getInitParameter("SPANNER_DATABASE"); - } } //try system properties if (INSTANCE_ID == null) { INSTANCE_ID = System.getProperty("SPANNER_INSTANCE"); } - if (DATABASE_ID == null) { - DATABASE_ID = System.getProperty("SPANNER_DATABASE"); - } if (DATABASE_ID == null) { DATABASE_ID = "db-" + UUID.randomUUID().toString().substring(0, 25); @@ -126,11 +121,11 @@ public void contextDestroyed(ServletContextEvent servletContextEvent) { databaseAdminClient = null; } - public static String getInstanceId() { + static String getInstanceId() { return INSTANCE_ID; } - public static String getDatabaseId() { + static String getDatabaseId() { return DATABASE_ID; } } diff --git a/appengine-java8/spanner/src/main/java/com/example/appengine/spanner/SpannerTasksServlet.java b/appengine-java8/spanner/src/main/java/com/example/appengine/spanner/SpannerTasksServlet.java index cb50d53e2e9..55fc380fe1c 100644 --- a/appengine-java8/spanner/src/main/java/com/example/appengine/spanner/SpannerTasksServlet.java +++ b/appengine-java8/spanner/src/main/java/com/example/appengine/spanner/SpannerTasksServlet.java @@ -44,7 +44,8 @@ * Individual tasks can be run using "tasks" query parameter. {@link SpannerTasks.Task} lists * supported tasks. All tasks are run in order if no parameter or "tasks=all" is provided. */ -@WebServlet(value = "/run") +// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required. +@WebServlet(value = "/spanner") public class SpannerTasksServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp)