diff --git a/2-structured-data/pom.xml b/2-structured-data/pom.xml
new file mode 100644
index 000000000..0bc5616b9
--- /dev/null
+++ b/2-structured-data/pom.xml
@@ -0,0 +1,86 @@
+
+
+
+ 4.0.0
+ war
+ 1.0-SNAPSHOT
+ com.example.appengine.gettingstartedjava
+ bookshelf2
+
+
+ javax.servlet
+ javax.servlet-api
+ 3.1.0
+ jar
+ provided
+
+
+ com.google.gcloud
+ gcloud-java-datastore
+ 0.0.12
+
+
+ jstl
+ jstl
+ 1.2
+
+
+ taglibs
+ standard
+ 1.1.2
+
+
+ org.apache.commons
+ commons-dbcp2
+ 2.1.1
+
+
+ mysql
+ mysql-connector-java
+ 5.1.37
+
+
+
+
+ ${project.build.directory}/${project.build.finalName}/WEB-INF/classes
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+ 2.6
+
+ false
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.3
+
+ 1.7
+ 1.7
+
+
+
+ com.google.appengine
+ gcloud-maven-plugin
+ 2.0.9.89.v20151202
+
+
+
+
diff --git a/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/RootServlet.java b/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/RootServlet.java
new file mode 100644
index 000000000..03d8df7bf
--- /dev/null
+++ b/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/RootServlet.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright 2015 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.gettingstartedjava;
+
+import com.example.appengine.gettingstartedjava.daos.BookDao;
+import com.example.appengine.gettingstartedjava.daos.CloudSqlDao;
+import com.example.appengine.gettingstartedjava.daos.DatastoreDao;
+
+import java.io.IOException;
+import java.sql.SQLException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@SuppressWarnings("serial")
+public class RootServlet extends HttpServlet {
+
+ @Override
+ public void init() throws ServletException {
+ String storageType = System.getenv("AEV2_JAVA_STORAGETYPE");
+ BookDao dao = null;
+ switch (storageType) {
+ case "datastore":
+ dao = new DatastoreDao();
+ break;
+ case "cloudsql":
+ try {
+ dao = new CloudSqlDao();
+ } catch (SQLException e) {
+ throw new ServletException("SQL error", e);
+ }
+ break;
+ default:
+ throw new IllegalStateException("Invalid storage type. Check if environment variable is set.");
+ }
+ this.getServletContext().setAttribute("dao", dao);
+ }
+
+ @Override
+ public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException,
+ ServletException {
+ req.getRequestDispatcher("/books").forward(req, resp);
+ }
+}
diff --git a/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/daos/BookDao.java b/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/daos/BookDao.java
new file mode 100644
index 000000000..f66ff62ae
--- /dev/null
+++ b/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/daos/BookDao.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright 2015 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.gettingstartedjava.daos;
+
+import com.example.appengine.gettingstartedjava.objects.Book;
+import com.example.appengine.gettingstartedjava.objects.Result;
+
+public interface BookDao {
+
+ public Long createBook(Book book) throws Exception;
+
+ public Book readBook(Long bookId) throws Exception;
+
+ public void updateBook(Book book) throws Exception;
+
+ public void deleteBook(Long bookId) throws Exception;
+
+ public Result listBooks(String startCursor) throws Exception;
+}
diff --git a/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/daos/CloudSqlDao.java b/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/daos/CloudSqlDao.java
new file mode 100644
index 000000000..f6da47491
--- /dev/null
+++ b/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/daos/CloudSqlDao.java
@@ -0,0 +1,147 @@
+/**
+ * Copyright 2015 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.gettingstartedjava.daos;
+
+import org.apache.commons.dbcp2.BasicDataSource;
+
+import com.example.appengine.gettingstartedjava.objects.Book;
+import com.example.appengine.gettingstartedjava.objects.Result;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+
+public class CloudSqlDao implements BookDao {
+
+ private static final BasicDataSource dataSource = new BasicDataSource();
+
+ public CloudSqlDao() throws SQLException {
+ final String url = System.getenv("SQL_DATABASE_URL");
+ dataSource.setUrl(url);
+ final String createTableSql = "CREATE TABLE IF NOT EXISTS books ( id INT NOT NULL "
+ + "AUTO_INCREMENT, author VARCHAR(255), description VARCHAR(255), publishedDate "
+ + "VARCHAR(255), title VARCHAR(255), PRIMARY KEY (id))";
+ try (Connection conn = dataSource.getConnection()) {
+ conn.createStatement().executeUpdate(createTableSql);
+ }
+ }
+
+ @Override
+ public Long createBook(Book book) throws SQLException {
+ final String createBookString = "INSERT INTO books (author, description, publishedDate, title) "
+ + "VALUES (?, ?, ?, ?)";
+ try (Connection conn = dataSource.getConnection();
+ final PreparedStatement createBookStmt = conn.prepareStatement(createBookString,
+ Statement.RETURN_GENERATED_KEYS)) {
+ createBookStmt.setString(1, book.getAuthor());
+ createBookStmt.setString(2, book.getDescription());
+ createBookStmt.setString(3, book.getPublishedDate());
+ createBookStmt.setString(4, book.getTitle());
+ createBookStmt.executeUpdate();
+ try (ResultSet keys = createBookStmt.getGeneratedKeys()) {
+ keys.next();
+ return keys.getLong(1);
+ }
+ }
+ }
+
+ @Override
+ public Book readBook(Long bookId) throws SQLException {
+ final String readBookString = "SELECT * FROM books WHERE id = ?";
+ try (Connection conn = dataSource.getConnection();
+ PreparedStatement readBookStmt = conn.prepareStatement(readBookString)) {
+ readBookStmt.setLong(1, bookId);
+ try (ResultSet keys = readBookStmt.executeQuery()) {
+ keys.next();
+ return new Book.Builder()
+ .author(keys.getString("author"))
+ .description(keys.getString("description"))
+ .id(keys.getLong("id"))
+ .publishedDate(keys.getString("publishedDate"))
+ .title(keys.getString("title"))
+ .build();
+ }
+ }
+ }
+
+ @Override
+ public void updateBook(Book book) throws SQLException {
+ final String updateBookString = "UPDATE books SET author = ?, description = ?, "
+ + "publishedDate = ?, title = ? WHERE id = ?";
+ try (Connection conn = dataSource.getConnection();
+ PreparedStatement updateBookStmt = conn.prepareStatement(updateBookString)) {
+ updateBookStmt.setString(1, book.getAuthor());
+ updateBookStmt.setString(2, book.getDescription());
+ updateBookStmt.setString(3, book.getPublishedDate());
+ updateBookStmt.setString(4, book.getTitle());
+ updateBookStmt.setLong(5, book.getId());
+ updateBookStmt.executeUpdate();
+ }
+ }
+
+ @Override
+ public void deleteBook(Long bookId) throws SQLException {
+ final String deleteBookString = "DELETE FROM books WHERE id = ?";
+ try (Connection conn = dataSource.getConnection();
+ PreparedStatement deleteBookStmt = conn.prepareStatement(deleteBookString)) {
+ deleteBookStmt.setLong(1, bookId);
+ deleteBookStmt.executeUpdate();
+ }
+ }
+
+ @Override
+ public Result listBooks(String cursor) throws SQLException {
+ int offset = 0;
+ if (cursor != null && !cursor.equals("")) {
+ offset = Integer.parseInt(cursor);
+ }
+ final String listBooksString = "SELECT SQL_CALC_FOUND_ROWS author, description, id, "
+ + "publishedDate, title FROM books ORDER BY title ASC LIMIT 10 OFFSET ?";
+ try (Connection conn = dataSource.getConnection();
+ PreparedStatement listBooksStmt = conn.prepareStatement(listBooksString)) {
+ listBooksStmt.setInt(1, offset);
+ List resultBooks = new ArrayList<>();
+ try (ResultSet rs = listBooksStmt.executeQuery()) {
+ while (rs.next()) {
+ Book book = new Book.Builder()
+ .author(rs.getString("author"))
+ .description(rs.getString("description"))
+ .id(rs.getLong("id"))
+ .publishedDate(rs.getString("publishedDate"))
+ .title(rs.getString("title"))
+ .build();
+ resultBooks.add(book);
+ }
+ }
+ try (ResultSet rs = conn.createStatement().executeQuery("SELECT FOUND_ROWS()")) {
+ int totalNumRows = 0;
+ if (rs.next()) {
+ totalNumRows = rs.getInt(1);
+ }
+ if (totalNumRows > offset + 10) {
+ return new Result<>(resultBooks, Integer.toString(offset + 10));
+ } else {
+ return new Result<>(resultBooks);
+ }
+ }
+ }
+ }
+}
diff --git a/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/daos/DatastoreDao.java b/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/daos/DatastoreDao.java
new file mode 100644
index 000000000..1fd97db8a
--- /dev/null
+++ b/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/daos/DatastoreDao.java
@@ -0,0 +1,128 @@
+/**
+ * Copyright 2015 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.gettingstartedjava.daos;
+
+import com.google.gcloud.datastore.Cursor;
+import com.google.gcloud.datastore.Datastore;
+import com.google.gcloud.datastore.DatastoreOptions;
+import com.google.gcloud.datastore.Entity;
+import com.google.gcloud.datastore.FullEntity;
+import com.google.gcloud.datastore.IncompleteKey;
+import com.google.gcloud.datastore.Key;
+import com.google.gcloud.datastore.KeyFactory;
+import com.google.gcloud.datastore.Query;
+import com.google.gcloud.datastore.QueryResults;
+import com.google.gcloud.datastore.StructuredQuery.OrderBy;
+
+import com.example.appengine.gettingstartedjava.objects.Book;
+import com.example.appengine.gettingstartedjava.objects.Result;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DatastoreDao implements BookDao {
+
+ private Datastore datastore;
+ private KeyFactory keyFactory;
+
+ public DatastoreDao() {
+ datastore = DatastoreOptions
+ .builder()
+ .projectId(System.getenv("PROJECT_ID"))
+ .build()
+ .service();
+ keyFactory = datastore.newKeyFactory().kind("Book");
+ }
+
+ @Override
+ public Long createBook(Book book) {
+ IncompleteKey key = keyFactory.kind("Book").newKey();
+ FullEntity incBookEntity = Entity.builder(key)
+ .set("author", book.getAuthor())
+ .set("description", book.getDescription())
+ .set("publishedDate", book.getPublishedDate())
+ .set("title", book.getTitle())
+ .build();
+ Entity bookEntity = datastore.add(incBookEntity);
+ return bookEntity.key().id();
+ }
+
+ @Override
+ public Book readBook(Long bookId) {
+ Entity bookEntity = datastore.get(keyFactory.newKey(bookId));
+ return new Book.Builder()
+ .author(bookEntity.getString("author"))
+ .description(bookEntity.getString("description"))
+ .id(bookEntity.key().id())
+ .publishedDate(bookEntity.getString("publishedDate"))
+ .title(bookEntity.getString("title"))
+ .build();
+ }
+
+ @Override
+ public void updateBook(Book book) {
+ Key key = keyFactory.newKey(book.getId());
+ Entity entity = Entity.builder(key)
+ .set("author", book.getAuthor())
+ .set("description", book.getDescription())
+ .set("publishedDate", book.getPublishedDate())
+ .set("title", book.getTitle())
+ .build();
+ datastore.update(entity);
+ }
+
+ @Override
+ public void deleteBook(Long bookId) {
+ Key key = keyFactory.newKey(bookId);
+ datastore.delete(key);
+ }
+
+ @Override
+ public Result listBooks(String startCursorString) {
+ Query q;
+ Cursor startCursor = null;
+ if(startCursorString != null && !startCursorString.equals("")) {
+ startCursor = Cursor.fromUrlSafe(startCursorString);
+ }
+ q = Query.entityQueryBuilder()
+ .kind("Book")
+ .limit(10)
+ .startCursor(startCursor)
+ .orderBy(OrderBy.asc("title"))
+ .build();
+ QueryResults resultList = datastore.run(q);
+ List resultBooks = new ArrayList<>();
+ while(resultList.hasNext()) {
+ Entity bookEntity = resultList.next();
+ Book book = new Book.Builder()
+ .author(bookEntity.getString("author"))
+ .description(bookEntity.getString("description"))
+ .id(bookEntity.key().id())
+ .publishedDate(bookEntity.getString("publishedDate"))
+ .title(bookEntity.getString("title"))
+ .build();
+ resultBooks.add(book);
+ }
+ Cursor cursor = resultList.cursorAfter(); // note cursorAfter() doesn't work currently
+ if(cursor != null) {
+ String cursorString = cursor.toUrlSafe();
+ return new Result<>(resultBooks, cursorString);
+ } else {
+ return new Result<>(resultBooks);
+ }
+ }
+}
diff --git a/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/objects/Book.java b/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/objects/Book.java
new file mode 100644
index 000000000..22a2e13ce
--- /dev/null
+++ b/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/objects/Book.java
@@ -0,0 +1,117 @@
+/**
+ * Copyright 2015 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.gettingstartedjava.objects;
+
+public class Book {
+
+ private String title;
+ private String author;
+ private String publishedDate;
+ private String description;
+ private Long id;
+
+ // We use a Builder pattern here to simplify and standardize construction of Book objects.
+ private Book(Builder b) {
+ this.title = b.title;
+ this.author = b.author;
+ this.publishedDate = b.publishedDate;
+ this.description = b.description;
+ this.id = b.id;
+ }
+
+ public static class Builder {
+ private String title;
+ private String author;
+ private String publishedDate;
+ private String description;
+ private Long id;
+
+ public Builder title(String title) {
+ this.title = title;
+ return this;
+ }
+
+ public Builder author(String author) {
+ this.author = author;
+ return this;
+ }
+
+ public Builder publishedDate(String publishedDate) {
+ this.publishedDate = publishedDate;
+ return this;
+ }
+
+ public Builder description(String description) {
+ this.description = description;
+ return this;
+ }
+
+ public Builder id(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public Book build() {
+ return new Book(this);
+ }
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getAuthor() {
+ return author;
+ }
+
+ public void setAuthor(String author) {
+ this.author = author;
+ }
+
+ public String getPublishedDate() {
+ return publishedDate;
+ }
+
+ public void setPublishedDate(String publishedDate) {
+ this.publishedDate = publishedDate;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ @Override
+ public String toString() {
+ return author;
+ }
+}
diff --git a/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/objects/Result.java b/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/objects/Result.java
new file mode 100644
index 000000000..9f48b9a79
--- /dev/null
+++ b/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/objects/Result.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright 2015 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.gettingstartedjava.objects;
+
+import java.util.List;
+
+public class Result {
+
+ public String cursor;
+ public List result;
+
+ public Result(List result, String cursor) {
+ this.result = result;
+ this.cursor = cursor;
+ }
+
+ public Result(List result) {
+ this.result = result;
+ this.cursor = null;
+ }
+}
diff --git a/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/servlets/CreateBookServlet.java b/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/servlets/CreateBookServlet.java
new file mode 100644
index 000000000..e2f2653e5
--- /dev/null
+++ b/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/servlets/CreateBookServlet.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright 2015 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.gettingstartedjava.servlets;
+
+import com.example.appengine.gettingstartedjava.daos.BookDao;
+import com.example.appengine.gettingstartedjava.objects.Book;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@SuppressWarnings("serial")
+@WebServlet(name = "create", value = "/create")
+public class CreateBookServlet extends HttpServlet {
+
+ @Override
+ public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
+ IOException {
+ req.setAttribute("action", "Add");
+ req.setAttribute("destination", "create");
+ req.getRequestDispatcher("/form.jsp").forward(req, resp);
+ }
+
+ @Override
+ public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
+ IOException {
+ BookDao dao = (BookDao) this.getServletContext().getAttribute("dao");
+ Book book = new Book.Builder()
+ .author(req.getParameter("author"))
+ .description(req.getParameter("description"))
+ .publishedDate(req.getParameter("publishedDate"))
+ .title(req.getParameter("title"))
+ .build();
+ try {
+ Long id = dao.createBook(book);
+ resp.sendRedirect("/read?id="+id.toString());
+ } catch (Exception e) {
+ throw new ServletException("Error creating book", e);
+ }
+ }
+}
diff --git a/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/servlets/DeleteBookServlet.java b/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/servlets/DeleteBookServlet.java
new file mode 100644
index 000000000..19d44a857
--- /dev/null
+++ b/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/servlets/DeleteBookServlet.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright 2015 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.gettingstartedjava.servlets;
+
+import com.example.appengine.gettingstartedjava.daos.BookDao;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@SuppressWarnings("serial")
+@WebServlet(name = "delete", value = "/delete")
+public class DeleteBookServlet extends HttpServlet {
+
+ @Override
+ public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
+ IOException {
+ Long id = Long.decode(req.getParameter("id"));
+ BookDao dao = (BookDao) this.getServletContext().getAttribute("dao");
+ try {
+ dao.deleteBook(id);
+ resp.sendRedirect("/books");
+ } catch (Exception e) {
+ throw new ServletException("Error deleting book", e);
+ }
+ }
+}
diff --git a/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/servlets/ListBookServlet.java b/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/servlets/ListBookServlet.java
new file mode 100644
index 000000000..f2e52745f
--- /dev/null
+++ b/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/servlets/ListBookServlet.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright 2015 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.gettingstartedjava.servlets;
+
+import com.example.appengine.gettingstartedjava.daos.BookDao;
+import com.example.appengine.gettingstartedjava.objects.Book;
+import com.example.appengine.gettingstartedjava.objects.Result;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@SuppressWarnings("serial")
+@WebServlet(name = "list", value = "/books")
+public class ListBookServlet extends HttpServlet {
+
+ @Override
+ public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException,
+ ServletException {
+ BookDao dao = (BookDao) this.getServletContext().getAttribute("dao");
+ String startCursor = req.getParameter("cursor");
+ List books = null;
+ String endCursor = null;
+ try {
+ Result result = dao.listBooks(startCursor);
+ books = result.result;
+ endCursor = result.cursor;
+ } catch (Exception e) {
+ throw new ServletException("Error listing books", e);
+ }
+ req.getSession().getServletContext().setAttribute("books", books);
+ req.setAttribute("cursor", endCursor);
+ req.getRequestDispatcher("/base.jsp").forward(req, resp);
+ }
+}
diff --git a/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/servlets/ReadBookServlet.java b/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/servlets/ReadBookServlet.java
new file mode 100644
index 000000000..cb7cd1a75
--- /dev/null
+++ b/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/servlets/ReadBookServlet.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright 2015 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.gettingstartedjava.servlets;
+
+import com.example.appengine.gettingstartedjava.daos.BookDao;
+import com.example.appengine.gettingstartedjava.objects.Book;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@SuppressWarnings("serial")
+@WebServlet(name = "read", value = "/read")
+public class ReadBookServlet extends HttpServlet {
+
+ @Override
+ public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException,
+ ServletException {
+ Long id = Long.decode(req.getParameter("id"));
+ BookDao dao = (BookDao) this.getServletContext().getAttribute("dao");
+ try {
+ Book book = dao.readBook(id);
+ req.setAttribute("book", book);
+ req.getRequestDispatcher("/view.jsp").forward(req, resp);
+ } catch (Exception e) {
+ throw new ServletException("Error reading book", e);
+ }
+ }
+}
diff --git a/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/servlets/UpdateBookServlet.java b/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/servlets/UpdateBookServlet.java
new file mode 100644
index 000000000..c22ae3678
--- /dev/null
+++ b/2-structured-data/src/main/java/com/example/appengine/gettingstartedjava/servlets/UpdateBookServlet.java
@@ -0,0 +1,67 @@
+/**
+ * Copyright 2015 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.gettingstartedjava.servlets;
+
+import com.example.appengine.gettingstartedjava.daos.BookDao;
+import com.example.appengine.gettingstartedjava.objects.Book;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@SuppressWarnings("serial")
+@WebServlet(name = "update", value = "/update")
+public class UpdateBookServlet extends HttpServlet {
+
+ @Override
+ public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
+ IOException {
+ BookDao dao = (BookDao) this.getServletContext().getAttribute("dao");
+ try {
+ Book book = dao.readBook(Long.decode(req.getParameter("id")));
+ req.setAttribute("book", book);
+ req.setAttribute("action", "Edit");
+ req.setAttribute("destination", "update");
+ req.getRequestDispatcher("/form.jsp").forward(req, resp);
+ } catch (Exception e) {
+ throw new ServletException("Error loading book for editing", e);
+ }
+ }
+
+ @Override
+ public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
+ IOException {
+ BookDao dao = (BookDao) this.getServletContext().getAttribute("dao");
+ Book book = new Book.Builder()
+ .author(req.getParameter("author"))
+ .description(req.getParameter("description"))
+ .id(Long.decode(req.getParameter("id")))
+ .publishedDate(req.getParameter("publishedDate"))
+ .title(req.getParameter("title"))
+ .build();
+ try {
+ dao.updateBook(book);
+ resp.sendRedirect("/read?id=" + req.getParameter("id"));
+ } catch (Exception e) {
+ throw new ServletException("Error updating book", e);
+ }
+ }
+}
diff --git a/2-structured-data/src/main/webapp/WEB-INF/appengine-web.xml b/2-structured-data/src/main/webapp/WEB-INF/appengine-web.xml
new file mode 100644
index 000000000..341dbc579
--- /dev/null
+++ b/2-structured-data/src/main/webapp/WEB-INF/appengine-web.xml
@@ -0,0 +1,15 @@
+
+
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+
+
diff --git a/2-structured-data/src/main/webapp/WEB-INF/web.xml b/2-structured-data/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 000000000..6467df305
--- /dev/null
+++ b/2-structured-data/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,10 @@
+
+
+ root
+ com.example.appengine.gettingstartedjava.RootServlet
+
+
+ root
+ /
+
+
diff --git a/2-structured-data/src/main/webapp/base.jsp b/2-structured-data/src/main/webapp/base.jsp
new file mode 100644
index 000000000..e95ea8fc0
--- /dev/null
+++ b/2-structured-data/src/main/webapp/base.jsp
@@ -0,0 +1,57 @@
+<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
+<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
+
+
+
+ Bookshelf - Java on Google Cloud Platform
+
+
+
+
+
+