The Recipe application is a small but complete Java servlet that queries for recipes. It gives you an idea of how to construct an application that can run with Google Base queries.
You can run the Recipe application as a demo at http://base.google.com/base/api/demo/recipe/
Alternately, you can install and run the web application in /java/sample/gbase/lib/gdata-base-recipe.war on any application server that supports at least JSP 1.2 and Servlets
2.3, running under java 5. Follow the instructions provided by your application
server vendor.
Here are step-by-step instructions for downloading Tomcat 5 and installing
the recipe application:
/java/sample/gbase/lib/gdata-base-recipe.war) into Tomcat's webapps directory
and rename it recipe.warbin/catalina.sh run)Most of the code and all of the JSPs in the Recipe sample application use standard J2EE features (http://java.sun.com/javaee). You need to be familiar with servlets and JSPs to understand the code.
This section discusses the classes that contain the Recipe application code. The source files for the recipe application are in the /java/sample/gbase/recipe directory of the distribution.
AuthenticationFilter The key area for Google Base web applications is managing authentication correctly. Google has an authentication system that allows applications to use an authentication proxy interface called AuthSub to handle Google Base items on behalf of their users without ever knowing the user's password. Using the proxy, a user of the web application logs into their Google account and consents to grant limited access to the web application. For further information on how AuthSub works, refer to the AuthSub documentation and the Java Client Library Developer's Guide.
In the recipe code, an AuthenticationFilter object creates
and configures a GoogleBaseService as required by the AuthSub protocol.
The first time a page protected by the filter, AuthenticationFilter.doFilter redirects
the call to https://www.google.com/accounts/. Once
the user has successfully logged in and allowed the web application to
access their data, the Google authentication server forwards the browser
back to the web application:
private void redirectToAuthSub(HttpServletRequest request, HttpServletResponse response)
throws IOException {
StringBuffer next = request.getRequestURL();
String queryString = request.getQueryString();
if (queryString != null && !"".equals(queryString) ) {
next.append("?").append(queryString);
}
String scope = new URL(urlFactory.getBaseURL(), "feeds").toExternalForm();
String url = AuthSubUtil.getRequestUrl(authsubProtocol,
authsubHostname,
next.toString(),
scope,
false,
true);
response.sendRedirect(response.encodeRedirectURL(url));
}
The second time a page protected by AuthenticationFilter is
called, the request needs to contain the token parameter,
which is a single-use token generated by the Google authentication server.
In the following code, the filter:
private void startAuthenticatedSession(HttpServletRequest request,
HttpServletResponse response,
String oneTimeToken)
throws IOException, GeneralSecurityException, AuthenticationException,
ServletException {
String sessionToken = exchangeForSessionToken(oneTimeToken);
// Store the authentication token in a cookie
response.addCookie(newSessionTokenCookie(request, sessionToken));
// Redirect the browser to the same address
// with the "token=value" part removed from the query string
StringBuffer url = request.getRequestURL();
String queryString = request.getQueryString();
if (queryString != null) {
queryString = queryString.replaceFirst("token=[^&]*&?", "");
if (queryString.length() > 0) {
url.append("?").append(queryString);
}
}
response.sendRedirect(response.encodeRedirectURL(url.toString()));
}
protected String exchangeForSessionToken(String oneTimeToken)
throws IOException, GeneralSecurityException, AuthenticationException {
return AuthSubUtil.exchangeForSessionToken(authsubProtocol,
authsubHostname,
oneTimeToken,
null);
}
At this point, each time AuthenticationFilter is called,
it:
filterChain.doFilter. Servlets will find the GoogleBaseService object to use in a request attribute set by this filter.
GoogleBaseService service = new GoogleBaseService( applicationName, key, authsubProtocol, authsubHostname); service.setAuthSubToken(sessionToken); // Make the service available to the servlet httpRequest.setAttribute(SERVICE_ATTRIBUTE, service); // Execute the servlet filterChain.doFilter(request, response);
Servlets that don't access protected feeds don't need AuthenticationFilter.
They can create their own GoogleBaseService object and not set the AuthSub
token.
Servlets that access protected feeds need an AuthenticationFilter
provided for a Google account that was properly set up for Google Base.
RecipeDisplayServlet.java / Recipe.javaThis servlet gets the snippet of a specific query and sends the result to a JSP for it to be displayed. Flattened and simplified, the code in this servlet executes the following actions:
GoogleBaseService service = new GoogleBaseService(applicationName, developerKey); URL feedUrl = urlFactory.getSnippetsEntryURL(id); entry = service.getEntry(feedUrl); Recipe recipe = new Recipe(entry);
This code performs these actions:
The Recipe class is a simple application-specific wrapper around a Google
Base recipe. It shields the rest of the application from the Google Base
library and contains some logic specific to recipes. The most interesting
methods of the Recipe class are its constructor and the method toGoogleBaseEntry().
Here's the constructor:
public Recipe(GoogleBaseEntry entry) {
id = extractIdFromUrl(entry.getId());
title = entry.getTitle().getPlainText();
url = entry.getHtmlLink().getHref();
String description = null;
if (entry.getContent() != null) {
Content content = entry.getContent();
if (content instanceof TextContent) {
description = ((TextContent)content).getContent().getPlainText();
} else if (content instanceof OtherContent) {
description = ((OtherContent)content).getText();
}
}
...
Here's the toGoogleBaseEntry() method:
public GoogleBaseEntry toGoogleBaseEntry(String idUrl)
{
GoogleBaseEntry entry = new GoogleBaseEntry();
entry.getGoogleBaseAttributes().setItemType(RECIPE_ITEMTYPE);
if (idUrl != null) {
entry.setId(idUrl);
}
entry.setTitle(TextConstruct.create(TextConstruct.Type.TEXT, title, null));
if (url != null) {
entry.addHtmlLink(url, null, null);
}
if (description != null) {
// If the original content was not TEXT, the formatting is lost
entry.setContent(
TextConstruct.create(TextConstruct.Type.TEXT, description, null));
}
...
The toGoogleBaseEntry() method converts the GoogleBaseEntry
into a Recipe and then back into a GoogleBaseEntry object.
RecipeSearchServlet.java / RecipeSearch.javaThe Recipe servlet performs these actions:
The most interesting code is in RecipeSearch.runQuery() where
a query is created with RecipeSearch.createQueryString() and
then executed using GoogleBaseService.query(). The
RecipeSearch.createQuery() method creates the query and handles
paging using GoogleBaseQuery.setMaxResults() to set the
page length and GoogleBaseQuery.setStartIndex() to set the
current page:
GoogleBaseQuery query = new GoogleBaseQuery(queryUrl);
query.setMaxResults(maxResults);
if (startIndex > 0) {
// the first index is 1
query.setStartIndex(startIndex + 1);
}
The query string is then created in RecipeSearch.createQueryString and
passed to the query object, which is executed by GoogleBaseService.query() :
public void runQuery() throws IOException, ServiceException {
GoogleBaseQuery query = createQuery();
System.out.println("Searching: " + query.getUrl());
GoogleBaseFeed feed = service.query(query);
List result = new ArrayList(maxResults);
for (GoogleBaseEntry entry : feed.getEntries()) {
result.add(new Recipe(entry));
}
this.recipes = result;
total = feed.getTotalResults();
}
Just as in the display servlets, GoogleBaseEntry objects are immediately converted into Recipe objects.
Note: In some cases, the results returned by the Recipe application may not appear to match the query. This is because the application doesn't always show the entire document, so there may be attributes that are not shown that match the query.
RecipeActionServlet.javaWhen adding a new recipe, the application first creates and initializes
a Recipe object. When this object has been completely initialized, the
servlet can insert it into Google Base. This is done in RecipeActionServlet.recipeAdd():
protected void recipeAdd(GoogleBaseService service,
Recipe recipe)
throws IOException, ServiceException {
URL feedUrl = urlFactory.getItemsFeedURL();
GoogleBaseEntry entry = recipe.toGoogleBaseEntry(null);
service.insert(feedUrl, entry);
}
Updating a recipe works the same way. The big difference is that the Recipe object has been created from an old GoogleBaseEntry and already has an ID:
protected void recipeUpdate(GoogleBaseService service,
Recipe recipe)
throws ServiceException, IOException {
URL feedUrl = urlFactory.getItemsEntryURL(recipe.getId());
GoogleBaseEntry entry = recipe.toGoogleBaseEntry(feedUrl.toString());
service.update(feedUrl, entry);
}
RecipeUtil.javaThe recipe application handles two types of errors:
The application validates requests, and throws an error if a parameter has an invalid value. Presubmit errors are generated by the application and should be handled internally.
For example, RecipeSearchServlet.doGet() handles a RecipeValidationException if a query parameter, such as cooking time, start index, or max results, has an invalid value.
} catch (RecipeValidationException rve) {
// internal exception in the argument handling (possibly incorrect args)
RecipeUtil.forwardToErrorPage(request, response, rve.getMessage());
return;
The handling methods for Application errors, including forwardToErrorPage(), are implemented in RecipeUtil.java.
These are usually thrown by the methods in the GoogleBaseService class. Service errors are generated by Google Base and should be caught.
There are three main reasons a service errors might be thrown:
The Java Client library provides a ServiceErrors class. The methods in this class can be used to parse ServiceExceptions. The Java Client Library Developer's Guide provides more information about handling service errors, and the javadoc provides referential information about the provided methods.
RecipeSearchServlet.doGet() handles a ServiceException if an error originates from Google Base:
} catch (ServiceException se) {
// exception coming from Google Base
RecipeUtil.logServiceException(this, se);
RecipeUtil.forwardToErrorPage(request, response, se);
return;
Go to QueryExample