User Interface Walkthrough

In this walkthrough, you learn about the user interface for the Guestbook sample app, which is implemented in JSP. This section doesn't cover the parts of the JSP that are Cloud Datastore and Objectify-related, because you'll learn about these in Storing Data with Objectify and Cloud Datastore.

This page is part of a multi-page tutorial. To start from the beginning and see instructions for setting up, go to Creating a Guestbook.

Handling user submission with forms

When the user enters a text message and clicks Post Greeting, the POST request is sent to the SignGuestbookServlet servlet and is handled by its doPost method, which stores the message in Cloud Datastore. The form behavior is specified in the JSP file.

Understanding the JSP file

The JSP file guestbook.jsp is located in the subdirectory appengine-java-guestbook-multiphase-master/final/src/main/webapp/. Any file in webapp/ or in a subdirectory other than WEB-INF/ that has the file suffix .jsp is automatically mapped to a URL path consisting of the path to the .jsp file, including the filename. This JSP will be mapped automatically to the URL /guestbook.jsp. Here is the source code for /guestbook.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="" %>
<%@ page import="" %>
<%@ page import="" %>

<%@ page import="com.example.guestbook.Greeting" %>
<%@ page import="com.example.guestbook.Guestbook" %>
<%@ page import="com.googlecode.objectify.Key" %>
<%@ page import="com.googlecode.objectify.ObjectifyService" %>

<%@ page import="java.util.List" %>
<%@ taglib prefix="fn" uri="" %>

    <link type="text/css" rel="stylesheet" href="/stylesheets/main.css"/>


    String guestbookName = request.getParameter("guestbookName");
    if (guestbookName == null) {
        guestbookName = "default";
    pageContext.setAttribute("guestbookName", guestbookName);
    UserService userService = UserServiceFactory.getUserService();
    User user = userService.getCurrentUser();
    if (user != null) {
        pageContext.setAttribute("user", user);

<p>Hello, ${fn:escapeXml(user.nickname)}! (You can
    <a href="<%= userService.createLogoutURL(request.getRequestURI()) %>">sign out</a>.)</p>
    } else {
    <a href="<%= userService.createLoginURL(request.getRequestURI()) %>">Sign in</a>
    to include your name with greetings you post.</p>

    // Create the correct Ancestor key
      Key<Guestbook> 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<Greeting> greetings = ObjectifyService.ofy()
          .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.

    if (greetings.isEmpty()) {
<p>Guestbook '${fn:escapeXml(guestbookName)}' has no messages.</p>
    } else {
<p>Messages in Guestbook '${fn:escapeXml(guestbookName)}'.</p>
      // Look at all of our greetings
        for (Greeting greeting : greetings) {
            pageContext.setAttribute("greeting_content", greeting.content);
            String author;
            if (greeting.author_email == null) {
                author = "An anonymous person";
            } else {
                author = greeting.author_email;
                String author_id = greeting.author_id;
                if (user != null && user.getUserId().equals(author_id)) {
                    author += " (You)";
            pageContext.setAttribute("greeting_user", author);
<p><b>${fn:escapeXml(greeting_user)}</b> wrote:</p>

<form action="/sign" method="post">
    <div><textarea name="content" rows="3" cols="60"></textarea></div>
    <div><input type="submit" value="Post Greeting"/></div>
    <input type="hidden" name="guestbookName" value="${fn:escapeXml(guestbookName)}"/>
<form action="/guestbook.jsp" method="get">
    <div><input type="text" name="guestbookName" value="${fn:escapeXml(guestbookName)}"/></div>
    <div><input type="submit" value="Switch Guestbook"/></div>


This JSP imports the library for the App Engine Users service, and calls the service to detect whether the user is signed in. It fetches the user's "nickname," and gets the URLs that the user can visit to sign in or sign out.

The use of the guestbookName variable is interesting. The use of this variable supports the user's arbitrary choice of guestbook name, which means the user can make up and use different guestbooks on the fly. For example, the user can have one guestbook and messages for Robin Hood, another for Robin's Merry Men, and yet another for Friends of Friar Tuck. After the user chooses a name, messages are posted to and displayed from only that guestbook.

This makes use of the ancestor path feature of Cloud Datastore.

Note the use of escapeXML on the user-inputs to prevent scripting attacks.


The stylesheet used by the JSP file is main.css, which is a simple CSS file:

body {
    font-family: Verdana, Helvetica, sans-serif;
    background-color: #FFFFCC;