My favorites | English | Sign in

Google Base Data API (Labs)

Java Client Library - RecipeExample

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.

Contents

  1. Using the Recipe Application
  2. Stepping through the Recipe application code
    1. Authenticating users
    2. Displaying a recipe
    3. Running queries
    4. Inserting and updating items
    5. Handling errors

Using the Recipe application

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:

  1. Download Tomcat
  2. Download the Java Client library and unpack the distribution
  3. Copy the war file (/java/sample/gbase/lib/gdata-base-recipe.war) into Tomcat's webapps directory and rename it recipe.war
  4. Run tomcat (bin/catalina.sh run)
  5. Open the URL http://localhost:8080/recipe/

Stepping through the Recipe application code

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.

Authenticating users: 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:

  • connects to https://www.google.com/accounts/ to convert the single-use token into a multiple-use token
  • stores the token into a cookie, and
  • redirects to the initial URL
 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:

  • gets the multi-use token from the cookie
  • puts that into every request it makes to Google Base
  • passes it to the GoogleBaseService object it creates
  • executes the servlet code using 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.

Back to top

Displaying a recipe - RecipeDisplayServlet.java / Recipe.java

This 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:

  • first creates a GoogleBaseService object
  • gets the URL of a recipe given its numerical ID that was passed to the servlet as a parameter
  • converts it into a GoogleBaseEntry object
  • creates a Recipe object from it.

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.

Running queries - RecipeSearchServlet.java / RecipeSearch.java

The Recipe servlet performs these actions:

  • executes a search
  • reads the result
  • converts it into Recipe object
  • passes this object to a JSP file for it to be displayed.

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.

Inserting and updating recipes - RecipeActionServlet.java

When 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);
}

Handling errors - RecipeUtil.java

The recipe application handles two types of errors:

Application 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.

Service errors

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 service is unavailable and your request couldn't be processed. There is nothing you can do about such an error.
  • The authenticated user is forbidden to use the service because of not agreeing with the Google Base's Terms of Services or not setting up the account for Google Base. When the application receives such an error, it should direct the user to the Google Base site to fix the problem.
  • The request you sent is not correct. It might have invalid values for certain fields, or an invalid item type. When the application catches such an error, it should fix and resubmit the request. This can be done automatically, or by directing the user to fix the problem.

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