My favorites | English | Sign in

Google Analytics (Labs)

Google Analytics on App Engine in Java

Nick Mihailovski, Google Analytics API Team
October 2009

Contents

  1. Overview
  2. Model View Controller Design
    1. Model
    2. View
    3. Controller
  1. Retrieving Data
    1. Get the User ID
    2. Handle the Token
    3. Make Google Analytics API Requests
  2. Revoking a Token
  3. Reference

Introduction

This article describes how to make authenticated Google Data API requests using the App Engine Java SDK. It demonstrates how Analytics, Accounts, and App Engine can work together to create an application that can be shared across the Internet. You can use the example to build your own application, and even replace any of the App Engine components with ones native to your infrastructure. For example, you can use your own database instead of the App Engine Datastore. Also, while the example uses Google Analytics, the program can easily be modified to access any of the other Google Data APIs that require authorization.

To get the most out of this article:

Be familiar with...
  • The Java language.
  • How the Google Analytics Data Export API works.
  • The different authorization methods provided by the Google Accounts API.
  • How Google App Engine works.
Start out by...

Overview

This application returns the bounce rate and the number of entrances and bounces for the top 500 landing pages from a profile choosen by the user. It displays all that data in an HTML table which is visualized as a Treemap.

In order to access their Google Analytics data, users have to login. This process is known as authorization. Similarly, applications—which act on behalf of the user—must also be authorized to access that data. Because a Google Account can be linked to many Google Services, applications should avoid asking users for their username and passwords directly. Instead, applications can go through an authorization using OAuth or AuthSub to process with the Google Accounts API to retrieve an authorization token for a particular user. Once the process is complete, the token can be used multiple times to make authorized requests to the Google Analytics API.

This application retrieves data from Google Analytics in 3 steps:

  1. The user must first login.
  2. The application retrieves the token by:
    a. either recalling an existing token from the App Engine Datastore.
    b. or redirecting the user to get a new token from the Google Accounts API, after which it stores the token in the App Engine Datastore.
  3. The application gets data from the Google Analytics API with an authorized token.

Note: This application supports both unregistered AuthSub and OAuth authorization methods. If you are using this example to build your own application, using unregistered AuthSub simplifies getting started by not requiring you to register your domain. Once your application is ready to launch, you can easily register your domain and switch to using oAuth. See the README on how to make this switch.

Back to Top

Model View Controller Design

This application uses the Model View Controller pattern to separate data from logic from display. In this application, the data object (Model) is separated from the logic required to retrieve that data (Controller), which is also separated from the data's presentation to the end user (View). This makes the application flexible, so you can extend this sample without having to rewrite the entire program.

This diagram describes how the MVC pattern has been applied in this application.


MVC class diagram

Model

The GoogleData object is the Model. It holds all the application data as well as the login and token status for the user.

GoogleData Source

public class GoogleData {

  // Authentication data.
  private String authenticationUrl;
  private Boolean isLoggedIn;

  // Authorization data.
  private String authorizationUrl;
  private String authorizationErrorMessage;
  private Boolean tokenValid;

  // Google Analytics specific data.
  private String tableId;
  private List accountList;
  private List dataList;
  private String accountListError;
  private String dataListError;
  ...
  // Getters and setters for each member.
}

View

The three JSPs represent the application view, and convert the data in the GoogleData object into HTML. Each JSP corresponds to one of the 3 steps above.

Controller

This application has two controllers, the MainServlet and the AuthorizationServlet.

The MainServlet is the default controller for this application. Its purpose is to set as much data as possible in the GoogleData object depending on how far the user is in the login-authorization-result steps illustrated above. The MainServlet then forwards the GoogleData object to the appropriate view for the user to continue in the process. The GoogleDataManager object handles the logic to get the GoogleData object.

MainServlet Source

public class MainServlet extends HttpServlet {

  // URL that handles this servlet.
  public static final String MAIN_URL = "/main";

  // URLs of views this servlet redirects to.
  public static final String LOGIN_VIEW_URL = "/WEB-INF/views/loginView.jsp";
  public static final String AUTHORIZATION_VIEW_URL = "/WEB-INF/views/authorizationView.jsp";
  public static final String RESULTS_VIEW_URL = "/WEB-INF/views/resultsView.jsp";

  static final String APPLICATION_NAME = "Analytics-Java-Demo-v1";

  private String nextUrl;

  /**
   * Handles GET requests for the main application to retrieve data from the Google Analytics API.
   */
  public void doGet(HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException {

    GoogleDataManager googleDataManager = GoogleDataManagerFactory.getOauthManager(
        APPLICATION_NAME,
        request.getParameter("ids"));

    GoogleData googleData = googleDataManager.getGoogleData();

    // First, users must login to use the application.
    nextUrl = LOGIN_VIEW_URL;
    if (googleData.isLoggedIn()) {

      // Second, users must authorize this application to access their Google Analytics account.
      nextUrl = AUTHORIZATION_VIEW_URL;
      if (googleData.isTokenValid()) {

        // Third, users can see results.
        nextUrl = RESULTS_VIEW_URL;
      }
    }

    // Forward the data onto the JSP.
    request.setAttribute("googleData", googleData);
    ServletContext sc = getServletContext();
    RequestDispatcher dispatcher = sc.getRequestDispatcher(nextUrl);
    dispatcher.forward(request, response);
  }
}

The AuthorizationServlet controls the authorization processes for this application. The AuthManager object handles all the business logic of getting an authorized token to use with the Google Analytics API. If an error in the authorization process occurs, the error message is stored in a GoogleData object and forwarded onto the authorization.jsp page so the user can try to authorize again. If no errors occur, the AuthorizationServlet redirects to the MainServlet, which continues the process of getting data from the Google Analytics API.

AuthorizationServlet Source

public class AuthorizationServlet extends HttpServlet {

  // Base URL of the Authorization Servlet.
  public static final String AUTH_HANDLER = "/tokenHandler";
  // URL to revoke a token.
  public static final String REVOKE_TOKEN_HANDLER = "/tokenHandler?action=revoke";
  // URL to authorize the user.
  public static final String AUTHORIZATION_HANDLER = "/tokenHandler?action=authorize";

  /**
   * Handles GET requests to this servlet.
   */
  public void doGet(HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException {

    AuthManager authManager = AuthManagerFactory.getOAuthManager();
    String nextUrl = "";

    if (request.getParameter(authManager.getTokenParam()) != null) {
      // The user is being redirected back from google.com to exchange the AuthSub one time
      // use token for a session token.
      authManager.storeAuthorizedToken(request.getQueryString());
      nextUrl = MainServlet.MAIN_URL;

    } else if (request.getParameter("action") != null) {

      if (request.getParameter("action").equals("revoke")) {
        // The user is revoking their token.
        authManager.revokeToken();
        nextUrl = MainServlet.MAIN_URL;

      } else if (request.getParameter("action").equals("authorize")) {
        // The user is starting the AuthSub authorization process.
        nextUrl = authManager.getAuthorizationRedirectUrl(request.getRequestURL());
      }
    }

    GoogleData googleData = authManager.getGoogleData();

    if (googleData.getAuthorizationErrorMessage() != null) {
      // Forward users to the authorization page.
      request.setAttribute("googleData", googleData);
      ServletContext sc = getServletContext();
      RequestDispatcher dispatcher = sc.getRequestDispatcher(MainServlet.AUTHORIZATION_VIEW_URL);
      dispatcher.forward(request, response);
    } else {
      // Redirect users to the next page.
      response.sendRedirect(nextUrl);
    }
  }
}

Concept: Both the the GoogleDataManager and AuthManager are created through dependency injection by the GoogleDataManagerFactory and AuthManagerFactory respectively. This means that all the objects each manager depends on are passed (injected) through each object's constructor. This makes it easier to test the code and simpler to change the logic. For example, this application can use either AuthSub or OAuth authorization methods simply by passing a new AuthorizationService implementation through the factory methods.

GoogleDataManagerFactory Source

public class GoogleDataManagerFactory {

  private GoogleDataManagerFactory() {}

  /**
   * Builds a new GoogleDataManager class to retrieve data from the Google Analytics API
   * using OAuth authorization.
   */
  public static GoogleDataManager getOauthManager(String applicationName, String tableId) {
    return new GoogleDataManager(
        UserServiceFactory.getUserService(),
        new TokenDaoJdoImpl(
            PMF.getInstance()),
        new AnalyticsServiceWrapper(
            new AnalyticsService(applicationName),
            new AuthorizationServiceOauthImpl(
                GoogleDataManager.GOOGLE_DATA_SCOPE,
                new GoogleOAuthParameters()),
            tableId));
  }
}

AuthManagerFactory Source

public class AuthManagerFactory {

  private AuthManagerFactory() {}

  /**
   * Returns an AuthManager object that implements oAuth to get authentication tokens.
   */
  public static AuthManager getOauthManager() {
    return new AuthManager(
        UserServiceFactory.getUserService(),
        new AuthorizationServiceOauthImpl(
            GoogleDataManager.GOOGLE_DATA_SCOPE,
            new GoogleOAuthParameters()),
        new TokenDaoJdoImpl(
            PMF.getInstance()));
  }
}

Back to Top

Retrieving Data From the Google Analytics API

The application goes through the following steps to retrieve data from the Google Analytics API:

Step 1: Get The User ID

Users must first login so the application can obtain their user ID. If there is no user ID, users are sent to the login.jsp view.

The UserService instance returns a URL that allows users to either logout or login. This URL is stored in the GoogleData object and when forwarded to the login.jsp view, an HTML link is displayed for users to authenticate. Once users authenticate with their Google Account, they are redirected back to the MainServlet.

GoogleDataManager getGoogleData method

public GoogleData getGoogleData() {
  ...
  // Get authentication data.
  String authenticationUrl = userService.isUserLoggedIn()
      ? userService.createLogoutURL(MainServlet.MAIN_URL)
      : userService.createLoginURL(MainServlet.MAIN_URL);
  googleData.setAuthenticationUrl(authenticationUrl);
  googleData.setIsLoggedIn(userService.isUserLoggedIn());
  ...
}

Step 2: Handle the Token

The authorization process handles the token by following a specific process:

A. Retrieve a Token From the Datastore

Once users have logged in and the MainServlet can obtain a user ID, the ID is used to retrieve an authorization token from the Datastore.

This application uses the TokenDaoJdoImpl class—which implements the TokenDao interface—to handle all interactions with the App Engine Datastore, including retrieving a token by user ID.

TokenJdoImpl retrieve token method

public UserToken retrieveTokenById(String userId) {
  PersistenceManager pm = getPersistenceManager();
  UserToken userToken = new UserToken();
  try {
    if (userId != null) {
      Key key = KeyFactory.createKey(UserToken.class.getSimpleName(), userId);
      userToken = pm.getObjectById(UserToken.class, key);
    }
  } catch (JDOObjectNotFoundException e) {
    return new UserToken(userId, null);
  }
  return userToken;
}

If a token doesn't exist, users are forwarded to the authorization.jsp.

Back to Top

B. Store a Token In The Datastore

The authorization.jsp view provides a link for users to get an authorization token. Once clicked, the link invokes the AuthorizationServlet, which redirects the user to google.com

The AuthManager class handles the logic for the authorization. Each authorization method (AuthSub and OAuth) has a distinct URL to google.com. This application supports both methods through the AuthorizationService interface, which defines the generic steps required for either authorization process. The actual authorization implementation is injected into the AuthManager through the AuthManagerFactory.

AuthService interface

public interface AuthorizationService {

  /**
   * Returns the authorization end point URL to redirect a user to so they can grant this
   * application access to their data.
   */
  public String getAuthorizationRedirectUrl(StringBuffer requestUrl);

  /**
   * Revokes the authorization token for the current user.
   */
  public void revokeToken(UserToken userToken);

  /**
   * Converts the single use token found in the requestQuery parameter into
   * a long lived session token.
   */
  public String getSessionTokenFromString(String requestQuery);

  /**
   * Returns an error message if any of the Authorization methods encountered an error.
   */
  public String getAuthServiceError();

  /**
   * Handles putting the sessionToken into the a GoogleService object. This task is defined here
   * because each authorization implementation has a different way of setting the token.
   */
  public void putTokenInGoogleService(UserToken userToken, GoogleService googleService);

  /**
   * Returns the name of the parameter that holds the token returned from the
   * Google Accounts service.
   */
  public String getTokenParam();
}

Once users are at the Google authorization page, they can grant the application access to their Google Analytics data. Once access is granted, users are then redirected back to the AuthorizationServlet with a token as a query parameter. The authorization servlet extracts this token and upgrades it to a long-lived token that doesn't expire and can be used on multiple Google Analytics API requests. The long-lived token is then stored in the App Engine Datastore along with the user ID. Application control is then returned to the MainServlet.

AuthManager storeToken method

public void storeAuthorizedToken(String requestQuery) {

  String userId = userService.getCurrentUser().getUserId();
  String sessionToken = authService.getSessionTokenFromString(requestQuery);

  UserToken tmpUserToken = new UserToken(userId, sessionToken);
  tokenDao.storeToken(tmpUserToken);
}

Step 3: Make Google Analytics API Requests

Once application control has been returned to the MainServlet, the long-lived authorization token exists in the Datastore. The token is retrieved using the user ID and set in the Google Analaytics API Java Client library AnalyticsService object.

In this application, the GoogleDataManager constructor handles all these three steps.

GoogleDataManager Constructor

public class GoogleDataManager {
  ...
  private UserService userService;
  private AnalyticsServiceWrapper analyticsWrapper;

  /**
   * Constructor.
   */
  public GoogleDataManager(UserService userService, TokenDao tokenDao,
      AnalyticsServiceWrapper analyticsWrapper) {
    this.userService = userService;
    this.analyticsWrapper = analyticsWrapper;

    // Get user id from logged in user.
    String userId = null;
    if (userService.getCurrentUser() != null) {
      userId = userService.getCurrentUser().getUserId();
    }
    // Initialize the AnalyticsServiceWrapper.
    analyticsWrapper.setToken(tokenDao.retrieveTokenById(userId));
  }
  ...
}

At this point, the application can attempt to make authorized requests to the Google Analytics API to retrieve data.

Note: Just because a token exists in the Datastore doesn't mean the token is valid. For example, users may have been revoked the token. This application checks if the token is valid by looking at the status code of the Google Analytics Export API Account Feed query. If the status code is 401, then the token is not valid, and the user is redirected to the authorization.jsp page. Alternatively, the Google Accounts API can be queried with a token to determine if the token is valid.

If the token is valid, requests to the API are made and the data is stored in a GoogleData object. The final GoogleData object with all the data is forwarded to the results page.

When the GoogleDataManager builds the GoogleData object, it calls the AnalyticsServiceWrapper to get the API data in the format it needs. The AnalyticsServiceWrapper creates API queries and uses the Google Data Java client library AnalyticsService class to make the actual requests to the Google Analytics API.

Back to Top

Revoking A Token

The results.jsp view provides the user a link to revoke a token. When clicked the link takes users to the AuthorizationServlet. The AuthorizationServlet revokes the token through the Google Accounts API and removes the token from the App Engine Datastore. Application control is returned to the MainServlet and users end up seeing the authorization.jsp page to get a new token.

This application handles the logic to revoke a token in the AuthManager revokeToken() method. This method retrieves the token using the user ID from the current user. Next, the AuthService revokes the token and finally the token is removed from the Datastore.

AuthManager revokeToken method

public void revokeToken() {
  String userId = userService.getCurrentUser().getUserId();
  UserToken tmpUserToken = tokenDao.retrieveTokenById(userId);
  authService.revokeToken(tmpUserToken);
  tokenDao.removeTokenById(userId);
}

Back to Top

Reference

Back to Top