Commits

Anonymous committed ffdaa37

[Issue 5414043] Adding Google Bigquery App Engine sample dashboard to samples repository.
http://codereview.appspot.com/5414043/

Comments (0)

Files changed (27)

bigquery-appengine-sample/.checkstyle

+<?xml version="1.0" encoding="UTF-8"?>
+
+<fileset-config file-format-version="1.2.0" simple-config="true" sync-formatter="false">
+  <fileset name="all" enabled="true" check-config-name="Google Checkstyle Checks (100)" local="false">
+    <file-match-pattern match-pattern="." include-pattern="true"/>
+  </fileset>
+</fileset-config>

bigquery-appengine-sample/.classpath

+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/war/WEB-INF/classes" path="src/main/java"/>
+	<classpathentry kind="src" output="target/war/WEB-INF/classes" path="src/main/resources"/>
+	<classpathentry kind="con" path="com.google.appengine.eclipse.core.GAE_CONTAINER"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
+	<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
+	<classpathentry kind="output" path="target/war/WEB-INF/classes"/>
+</classpath>

bigquery-appengine-sample/.project

+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>dashboard</name>
+	<comment>NO_M2ECLIPSE_SUPPORT: Project files created with the maven-eclipse-plugin are not supported in M2Eclipse.</comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>com.google.appengine.eclipse.core.projectValidator</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.google.gdt.eclipse.core.webAppProjectValidator</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.google.appengine.eclipse.core.enhancerbuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.m2e.core.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.m2e.core.maven2Nature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>com.google.appengine.eclipse.core.gaeNature</nature>
+	</natures>
+</projectDescription>

bigquery-appengine-sample/.settings/com.google.appengine.eclipse.core.prefs

+#Fri Nov 11 13:41:17 PST 2011
+eclipse.preferences.version=1
+filesCopiedToWebInfLib=appengine-api-labs-1.5.4.jar|jsr107cache-1.1.jar|appengine-api-1.0-sdk-1.5.4.jar|datanucleus-jpa-1.1.5.jar|jdo2-api-2.3-eb.jar|datanucleus-core-1.1.5.jar|geronimo-jpa_3.0_spec-1.1.1.jar|geronimo-jta_1.1_spec-1.1.1.jar|datanucleus-appengine-1.0.9.final.jar|appengine-jsr107cache-1.5.4.jar
+gaeDeployDialogSettings=

bigquery-appengine-sample/.settings/com.google.gdt.eclipse.core.prefs

+#Fri Nov 04 10:12:20 PDT 2011
+eclipse.preferences.version=1
+jarsExcludedFromWebInfLib=
+warSrcDir=target/war
+warSrcDirIsOutput=true

bigquery-appengine-sample/.settings/com.google.gwt.eclipse.core.prefs

+#Wed Nov 09 11:40:20 PST 2011
+eclipse.preferences.version=1
+entryPointModules=
+filesCopiedToWebInfLib=

bigquery-appengine-sample/.settings/org.eclipse.core.resources.prefs

+#Fri Nov 11 13:26:55 PST 2011
+eclipse.preferences.version=1
+encoding//src/main/java=UTF-8
+encoding//src/main/resources=UTF-8

bigquery-appengine-sample/.settings/org.eclipse.jdt.core.prefs

+#Fri Nov 11 13:26:55 PST 2011
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.source=1.5

bigquery-appengine-sample/.settings/org.eclipse.ltk.core.refactoring.prefs

+#Wed Nov 09 11:44:08 PST 2011
+eclipse.preferences.version=1
+org.eclipse.ltk.core.refactoring.enable.project.refactoring.history=false

bigquery-appengine-sample/.settings/org.eclipse.m2e.core.prefs

+#Fri Nov 11 13:26:41 PST 2011
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1

bigquery-appengine-sample/instructions.html

+<html>
+<title>Google Bigquery App Engine Dashboard Sample</title>
+<body>
+<h2>Instructions for the Google Bigquery App Engine Dashboard Sample</h2>
+
+<h3>Browse Source Code</h3>
+
+<ul>
+  <li><a
+    href="http://code.google.com/p/google-api-java-client/source/browse/bigquery-appengine-sample/?repo=samples">bigquery-appengine-sample</a>
+  or <a
+    href="http://code.google.com/p/google-api-java-client/source/browse/bigquery-appengine-sample/src/com/google/api/client/sample/bigquery/appengine/dashboard/ApplicationSettings.java?repo=samples">ApplicationSettings.java</a></li>
+</ul>
+
+<h3>Checkout Instructions</h3>
+
+<p><b>Prerequisites:</b> install <a href="http://java.com">Java 6</a>, <a
+  href="http://code.google.com/appengine/downloads.html">Google App Engine
+1.4.0</a>, <a href="http://mercurial.selenic.com/">Mercurial</a>, and <a
+  href="http://maven.apache.org/download.html">Maven</a>. You may need
+to set your <code>JAVA_HOME</code>.</p>
+
+<pre><code>cd <i>[someDirectory]</i>
+hg clone https://samples.google-api-java-client.googlecode.com/hg/ google-api-java-client-samples
+cd google-api-java-client-samples/bigquery-appengine-sample
+mvn clean package</code></pre>
+
+<h3>Setup Project in Eclipse 3.7</h3>
+
+<p><b>Prerequisites:</b> install <a href="http://www.eclipse.org/downloads/">Eclipse</a>,
+<a href="http://code.google.com/eclipse/">Google Plugin for Eclipse</a>, and the
+<a href="http://javaforge.com/project/HGE">Mercurial plugin</a>.</p>
+
+<ul>
+  <li>Import <code>bigquery-appengine-sample</code> project
+  <ul>
+    <li>File &gt; Import...</li>
+    <li>Select "General &gt; Existing Project into Workspace" and click
+    "Next"</li>
+    <li>Click "Browse" next to "Select root directory", find <code><i>[someDirectory]</i>/google-api-java-client-samples/bigquery-appengine-sample</code>
+    and click "Next"</li>
+    <li>Click "Finish"</li>
+  </ul>
+  </li>
+  <li>Settings
+  <ul>
+    <li>Add your Bigquery project ID as a system property to <code>bigquery-appengine-sample/src/main/webapp/WEB-INF/appengine-web.xml</code></li>
+    <li>Add the client ID and client secret of your project to <code>shared/shared-sample-appengine/src/main/resources/client_secrets.json</code>.  You can find your client id and secret on your <a href="https://code.google.com/apis/console">API console</a> for your Bigquery project</li>
+    <li>Run <code>mvn source:jar install</code> in <code>shared/shared-sample-appengine/</code></li>
+    <li>Run <code>mvn clean package</code> in <code>bigquery-appengine-sample/</code></li>
+  </ul>
+  </li>
+  <li>Run
+  <ul>
+    <li>Right-click on project bigquery-appengine-sample</li>
+    <li>Run As &gt; Web Application</li>
+  </ul>
+  </li>
+</ul>
+
+</body>
+</html>

bigquery-appengine-sample/pom.xml

+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>com.google</groupId>
+    <artifactId>google</artifactId>
+    <version>5</version>
+  </parent>
+  <groupId>com.google.api.client</groupId>
+  <artifactId>bigquery-appengine-sample</artifactId>
+  <packaging>war</packaging>
+  <version>1.0.0-SNAPSHOT</version>
+  <name>bigquery-appengine-sample</name>
+
+  <repositories>
+    <repository>
+      <id>google-api-services</id>
+      <url>http://mavenrepo.google-api-java-client.googlecode.com/hg</url>
+    </repository>
+  </repositories>
+
+  <pluginRepositories>
+    <pluginRepository>
+      <id>maven-gae-plugin-repo</id>
+      <name>Maven Google App Engine Repository</name>
+      <url>http://maven-gae-plugin.googlecode.com/svn/repository/</url>
+    </pluginRepository>
+  </pluginRepositories>
+
+  <properties>
+    <bigquery.version>v2-1.3.1-beta</bigquery.version>
+    <gae.version>1.5.4</gae.version>
+    <google-api-client.version>1.6.0-beta</google-api-client.version>
+    <webappDirectory>${project.build.directory}/${project.build.finalName}</webappDirectory>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <build>
+    <finalName>war</finalName>
+    <outputDirectory>${webappDirectory}/WEB-INF/classes</outputDirectory>
+
+    <plugins>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>2.3.2</version>
+        <configuration>
+          <source>1.5</source>
+          <target>1.5</target>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-war-plugin</artifactId>
+        <version>2.1.1</version>
+        <executions>
+          <execution>
+            <phase>compile</phase>
+            <goals>
+              <goal>exploded</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <webappDirectory>${webappDirectory}</webappDirectory>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>net.kindleit</groupId>
+      <artifactId>gae-runtime</artifactId>
+      <version>${gae.version}</version>
+      <type>pom</type>
+    </dependency>
+    <dependency>
+      <groupId>com.google.apis-samples</groupId>
+      <artifactId>shared-sample-appengine</artifactId>
+      <version>1.1.0</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.api-client</groupId>
+      <artifactId>google-api-client</artifactId>
+      <version>${google-api-client.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.api-client</groupId>
+      <artifactId>google-api-client-extensions</artifactId>
+      <version>${google-api-client.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.apis</groupId>
+      <artifactId>google-api-services-bigquery</artifactId>
+      <version>${bigquery.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>net.sf.jsr107cache</groupId>
+      <artifactId>jsr107cache</artifactId>
+      <version>1.1</version>
+    </dependency>
+  </dependencies>
+</project>

bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/AuthServlet.java

+// Copyright 2011 Google Inc. All Rights Reserved.
+
+package com.google.api.client.sample.bigquery.appengine.dashboard;
+
+import com.google.api.client.extensions.appengine.auth.AbstractAppEngineFlowServlet;
+import com.google.api.client.extensions.auth.helpers.ThreeLeggedFlow;
+import com.google.api.client.http.GenericUrl;
+import com.google.common.base.Preconditions;
+
+import java.io.IOException;
+
+import javax.jdo.PersistenceManagerFactory;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Servlet that handles authorization.
+ *
+ * @author lparkinson@google.com (Laura Parkinson)
+ */
+public abstract class AuthServlet extends AbstractAppEngineFlowServlet {
+
+  private String callbackUrl;
+
+  @Override
+  protected void service(HttpServletRequest request, HttpServletResponse response)
+      throws IOException, ServletException {
+    setCallbackUrl(request);
+    super.service(request, response);
+  }
+
+  @Override
+  protected PersistenceManagerFactory getPersistenceManagerFactory() {
+    return AuthUtils.PM_FACTORY;
+  }
+
+  @Override
+  protected ThreeLeggedFlow newFlow(String userId) throws SampleDashboardException {
+    AuthUtils authUtils = new AuthUtils(userId, getHttpTransport(), getJsonFactory());
+    return authUtils.newFlow(getCallbackUrl());
+  }
+
+  protected String getCallbackUrl() {
+    return Preconditions.checkNotNull(callbackUrl);
+  }
+
+  private void setCallbackUrl(HttpServletRequest request) {
+    if (callbackUrl == null) {
+      GenericUrl url = new GenericUrl(request.getRequestURL().toString());
+      url.setRawPath("/oauth2callback");
+      callbackUrl = url.build();
+    }
+  }
+}

bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/AuthUtils.java

+// Copyright 2011 Google Inc. All Rights Reserved.
+
+package com.google.api.client.sample.bigquery.appengine.dashboard;
+
+import com.google.api.client.extensions.auth.helpers.ThreeLeggedFlow;
+import com.google.api.client.extensions.auth.helpers.oauth2.draft10.OAuth2Credential;
+import com.google.api.client.googleapis.extensions.auth.helpers.oauth2.draft10.GoogleOAuth2ThreeLeggedFlow;
+import com.google.api.client.http.HttpTransport;
+import com.google.api.client.json.JsonFactory;
+import com.google.api.services.samples.shared.appengine.AppEngineUtils;
+import com.google.api.services.samples.shared.appengine.OAuth2ClientCredentials;
+
+import java.io.IOException;
+import java.util.logging.Logger;
+
+import javax.jdo.PersistenceManager;
+import javax.jdo.PersistenceManagerFactory;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author lparkinson@google.com (Laura Parkinson)
+ *
+ * Contains authorization variables and helper functions that multiple servlets need to access.
+ * Also supervises the OAuth tokens, refreshing them when asked and tracking the most
+ * recent access token.
+ */
+public class AuthUtils {
+
+  private static final Logger log = Logger.getLogger(AuthUtils.class.getName());
+
+  static final String BIGQUERY_SCOPE = "https://www.googleapis.com/auth/bigquery";
+  static final PersistenceManagerFactory PM_FACTORY =
+      AppEngineUtils.getPersistenceManagerFactory();
+
+  private String userId;
+  private HttpTransport transport;
+  private JsonFactory factory;
+
+  public AuthUtils(String userId, HttpTransport transport, JsonFactory factory) {
+    this.userId = userId;
+    this.transport = transport;
+    this.factory = factory;
+  }
+
+  public ThreeLeggedFlow newFlow(String callbackUrl) throws SampleDashboardException {
+    try {
+      return new GoogleOAuth2ThreeLeggedFlow(userId, OAuth2ClientCredentials.getClientId(),
+          OAuth2ClientCredentials.getClientSecret(), BIGQUERY_SCOPE, callbackUrl);
+    } catch (IOException ex) {
+      throw new SampleDashboardException(ex);
+    }
+  }
+
+  /**
+   * Removes the credentials for the given user from the datastore.
+   * @throws SampleDashboardException 
+   */
+  public void clearTokens() throws SampleDashboardException {
+    PersistenceManager persistenceManager = PM_FACTORY.getPersistenceManager();
+    try {
+      OAuth2Credential credential = getCredential(persistenceManager);
+      persistenceManager.deletePersistent(credential);
+    } finally {
+      persistenceManager.close();
+    }
+  }
+
+  /**
+   * Uses the refresh token in the datastore to get a new access token. Stores and returns it.
+   */
+  public String refreshAccessToken() {
+    String access = null;
+    PersistenceManager persistenceManager = PM_FACTORY.getPersistenceManager();
+    try {
+      OAuth2Credential credential = getCredential(persistenceManager);
+      credential.refresh(transport, factory);
+      access = credential.getAccessToken();
+    } catch (IOException e) {
+      log.warning("Unable to refresh access token.");
+      access = null;
+    } finally {
+      persistenceManager.close();
+    }
+    return access;
+  }
+
+  /**
+   * Retrieves the access token from the datastore.
+   * @throws SampleDashboardException 
+   */
+  public String getAccessToken() throws SampleDashboardException {
+    String access = null;
+    PersistenceManager persistenceManager = PM_FACTORY.getPersistenceManager();
+    try {
+      OAuth2Credential credential = getCredential(persistenceManager);
+      access = credential.getAccessToken();
+    } finally {
+      persistenceManager.close();
+    }
+    return access;
+  }
+
+  private OAuth2Credential getCredential(PersistenceManager persistenceManager)
+      throws SampleDashboardException {
+    ThreeLeggedFlow oauthFlow = newFlow(null);
+    oauthFlow.setJsonFactory(factory);
+    oauthFlow.setHttpTransport(transport);
+    return (OAuth2Credential) oauthFlow.loadCredential(persistenceManager);
+  }
+
+  /**
+   * Clears user credentials if a given exception is an unauthorized exception.
+   */
+  public boolean handleUnauthorizedException(SampleDashboardException ex) {
+    if (ex.getStatusCode() == HttpServletResponse.SC_UNAUTHORIZED) {
+      log.warning("User credentials didn't work, so they were deleted.");
+      try {
+        clearTokens();
+        return true;
+      } catch (SampleDashboardException ex2) {
+        return false;
+      }
+    }
+    return false;
+  }
+}

bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/BigqueryUtils.java

+// Copyright 2011 Google Inc. All Rights Reserved.
+
+package com.google.api.client.sample.bigquery.appengine.dashboard;
+
+import com.google.api.client.googleapis.auth.oauth2.draft10.GoogleAccessProtectedResource;
+import com.google.api.client.http.HttpTransport;
+import com.google.api.client.json.JsonFactory;
+import com.google.api.services.bigquery.Bigquery;
+import com.google.api.services.bigquery.model.Bigqueryfield;
+import com.google.api.services.bigquery.model.Job;
+import com.google.api.services.bigquery.model.Jobconfiguration;
+import com.google.api.services.bigquery.model.Jobconfigurationquery;
+import com.google.api.services.bigquery.model.Jobreference;
+import com.google.api.services.bigquery.model.Row;
+import com.google.api.services.bigquery.model.Table;
+import com.google.api.services.bigquery.model.TableDataList;
+import com.google.api.services.bigquery.model.Tablereference;
+import com.google.appengine.api.taskqueue.Queue;
+import com.google.appengine.api.taskqueue.QueueFactory;
+import com.google.appengine.api.taskqueue.RetryOptions;
+import com.google.appengine.api.taskqueue.TaskOptions;
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.logging.Logger;
+
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Utility methods for beginning jobs, waiting for jobs, and instantiating Bigqueries.
+ *
+ * @author lparkinson@google.com (Laura Parkinson)
+ */
+public class BigqueryUtils {
+
+  private static final Logger log = Logger.getLogger(BigqueryUtils.class.getName());
+  private static final String projectId =
+      System.getProperty("com.google.api.client.sample.bigquery.appengine.dashboard.projectId");
+
+  private final String userId;
+  private final Bigquery bigquery;
+  private Job job;
+  private final AuthUtils authUtils;
+  private final GoogleAccessProtectedResource gapr;
+
+  public BigqueryUtils(String userId, HttpTransport transport, JsonFactory jsonFactory)
+      throws SampleDashboardException {
+    this(userId, transport, jsonFactory, null);
+  }
+
+  public BigqueryUtils(String userId, HttpTransport transport, JsonFactory jsonFactory,
+      final String jobId) throws SampleDashboardException {
+    this.userId = userId;
+
+    authUtils = new AuthUtils(userId, transport, jsonFactory);
+    gapr = new GoogleAccessProtectedResource(authUtils.getAccessToken());
+    bigquery = Bigquery.builder(transport, jsonFactory).setHttpRequestInitializer(gapr).build();
+
+    if (jobId != null) {
+      job = tryToDo(new Callable<Job>() {
+        public Job call() throws Exception {
+          return bigquery.jobs().get(projectId, jobId).execute();
+        }
+      });
+
+      if (job == null) {
+        throw new SampleDashboardException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+            "Wasn't able to get a job for jobId " + jobId);
+      }
+    }
+  }
+
+  public void beginQuery() throws SampleDashboardException {
+    final Job queryJob = makeJob(buildExampleQuery());
+
+    job = tryToDo(new Callable<Job>() {
+      public Job call() throws Exception {
+        return bigquery.jobs().insert(queryJob).setProjectId(projectId).execute();
+      }
+    });
+
+    Preconditions.checkNotNull(job);
+    enqueueWaitingTask();
+  }
+
+  public boolean jobSucceeded() {
+    return (job != null && job.getStatus().getErrorResult() == null);
+  }
+
+  public String getJobErrorMessage() {
+    if (job != null && job.getStatus().getErrorResult() != null) {
+      return job.getStatus().getErrorResult().getMessage();
+    } else {
+      return "";
+    }
+  }
+
+  public boolean jobIsDone() {
+    String status = getJobStatus();
+    return (status != null && ("DONE").equalsIgnoreCase(status));
+  }
+
+  public String getJobStatus() {
+    return (job != null) ? job.getStatus().getState() : null;
+  }
+
+  public List<Bigqueryfield> getSchemaFieldNames() throws SampleDashboardException {
+    if (job != null) {
+      final Tablereference tableReference =
+          job.getConfiguration().getQuery().getDestinationTable();
+
+      Table table = tryToDo(new Callable<Table>() {
+        public Table call() throws IOException {
+          return bigquery.tables().get(tableReference.getProjectId(),
+              tableReference.getDatasetId(),
+              tableReference.getTableId()).execute();
+        }
+      });
+
+      Preconditions.checkNotNull(table);
+      Preconditions.checkNotNull(table.getSchema());
+      Preconditions.checkNotNull(table.getSchema().getFields());
+      return table.getSchema().getFields();
+    }
+    return null;
+  }
+
+  public List<Row> getTableData() throws SampleDashboardException {
+    if (job != null) {
+      final Tablereference tableReference =
+          job.getConfiguration().getQuery().getDestinationTable();
+
+      TableDataList tableDataList = tryToDo(new Callable<TableDataList>() {
+        public TableDataList call() throws IOException {
+          return bigquery.tabledata().list(tableReference.getProjectId(),
+              tableReference.getDatasetId(),
+              tableReference.getTableId()).execute();
+        }
+      });
+
+      Preconditions.checkNotNull(tableDataList);
+      Preconditions.checkNotNull(tableDataList.getRows());
+      return tableDataList.getRows();
+    }
+    return null;
+  }
+
+  /**
+   * Constructs a task with necessary parameters and options and puts it in
+   * App Engine's default task queue.
+   */
+  public void enqueueWaitingTask() {
+    TaskOptions options = TaskOptions.Builder.withDefaults();
+    options.param("jobId", job.getJobReference().getJobId());
+    options.param("userId", userId);
+    options.url("/task");
+    options.countdownMillis(1000);
+    options.retryOptions(RetryOptions.Builder.withTaskRetryLimit(0));
+
+    Queue queue = QueueFactory.getDefaultQueue();
+    queue.add(options);
+  }
+
+  public static String buildExampleQuery() {
+    String[] west = {"WA", "OR", "CA", "AK", "HI", "ID", "MT", "WY", "NV", "UT", "CO", "AZ", "NM"};
+    String[] south = {"OK", "TX", "AR", "LA", "TN", "MS", "AL", "KY", "GA", "FL", "SC", "NC", "VA",
+        "WV", "MD", "DC", "DE"};
+    String[] midwest = {"ND", "SD", "NE", "KS", "MN", "IA", "MO", "WI", "IL", "IN", "MI", "OH"};
+    String[] northeast = {"NY", "PA", "NJ", "CT", "RI", "MA", "VT", "NH", "ME"};
+
+    Joiner joiner = Joiner.on("', '");
+
+    String query = "SELECT IF (state IN ('" + joiner.join(west) + "'), 'West', \n\t"
+        + "IF (state IN ('" + joiner.join(south) + "'), 'South', \n\t"
+        + "IF (state IN ('" + joiner.join(midwest) + "'), 'Midwest', \n\t"
+        + "IF (state IN ('" + joiner.join(northeast) + "'), 'Northeast', 'None')))) "
+        + "as region, \naverage_mother_age, \naverage_father_age, \nstate, \nyear \n"
+        + "FROM (SELECT year, \n\t\tstate, \n\t\tSUM(mother_age)/COUNT(mother_age) as "
+        + "average_mother_age, \n\t\tSUM(father_age)/COUNT(father_age) as average_father_age \n\t"
+        + "FROM publicdata:samples.natality \n\tWHERE father_age < 99 \n\tGROUP BY year, state) \n"
+        + "ORDER BY year, region;";
+
+    return query;
+  }
+
+  /**
+   * Instantiates an example job and sets required fields.
+   */
+  private Job makeJob(String query) {
+    Jobconfigurationquery jobconfigurationquery = new Jobconfigurationquery();
+
+    jobconfigurationquery.setQuery(query);
+    jobconfigurationquery.setCreateDisposition("CREATE_IF_NEEDED");
+
+    Jobconfiguration jobconfiguration = new Jobconfiguration();
+    jobconfiguration.setQuery(jobconfigurationquery);
+
+    Jobreference jobreference = new Jobreference();
+    jobreference.setProjectId(projectId);
+
+    Job newJob = new Job();
+    newJob.setConfiguration(jobconfiguration);
+    newJob.setJobReference(jobreference);
+
+    return newJob;
+  }
+
+  /**
+   * Attempts to run the given callback with a number of retries. If the callback
+   * responds with SC_UNAUTHORIZED, the tokens are refreshed.
+   * @throws SampleDashboardException
+   */
+  private <T> T tryToDo(Callable<T> callback) throws SampleDashboardException {
+    int retries = 3;
+    int currentTry = 0;
+    SampleDashboardException sdex = null;
+    while (currentTry < retries) {
+      currentTry ++;
+      try {
+        return callback.call();
+      } catch (Exception ex) {
+        sdex = new SampleDashboardException(ex);
+        log.warning("Caught exception (" + sdex.getStatusCode() + "): " + sdex.getMessage());
+
+        if (sdex.getStatusCode() == HttpServletResponse.SC_UNAUTHORIZED) {
+          updateAccess(authUtils.refreshAccessToken());
+        }
+      }
+    }
+    throw Preconditions.checkNotNull(sdex);
+  }
+
+  private void updateAccess(String access) {
+    if (access != null) {
+      gapr.setAccessToken(access);
+    }
+  }
+}

bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/DataServlet.java

+// Copyright 2011 Google Inc. All Rights Reserved.
+
+package com.google.api.client.sample.bigquery.appengine.dashboard;
+
+import com.google.appengine.api.datastore.Entity;
+import com.google.appengine.api.users.UserServiceFactory;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * This servlet responds to a post request with the data in the datastore for the
+ * user in the form of json parseable by a DataTable constructor.  Also returns
+ * the stored message and whether their query failed.
+ *
+ * @author lparkinson@google.com (Laura Parkinson)
+ */
+public class DataServlet extends HttpServlet {
+
+  // It's important that the first column be a string and the second a number.
+  // Also, it is expected that these are the same length.
+  private final String[] labels = new String[]
+      {"State", "Year", "Average Mother Age", "Average Father Age", "U.S. Census Region"};
+  private final String[] properties = new String[]
+      {"state", "year", "average_mother_age", "average_father_age", "region"};
+  private final String[] types = new String[] {"string", "number", "number", "number", "string"};
+
+  /**
+   * Attempts to retrieve results for the logged-in user.  If the datastore contains
+   * results, they are written into the response as JSON.
+   */
+  @Override
+  protected void doPost(HttpServletRequest request, HttpServletResponse response)
+      throws IOException {
+    JsonWriter jsonWriter = new JsonWriter(response.getWriter()).beginObject();
+    String userId = UserServiceFactory.getUserService().getCurrentUser().getUserId();
+    DatastoreUtils datastoreUtils = new DatastoreUtils(userId);
+
+    String jobStatus = datastoreUtils.getUserJobStatus();
+
+    if (("DONE").equalsIgnoreCase(jobStatus)) {
+      List<Entity> results = datastoreUtils.getResults();
+      if (!results.isEmpty()) {
+        writeResultsToMotionChartJson(jsonWriter, results);
+      }
+    }
+
+    jsonWriter.name("failed").value(datastoreUtils.hasUserQueryFailed());
+    jsonWriter.name("message").value(datastoreUtils.getUserMessage());
+    jsonWriter.name("lastRun").value(datastoreUtils.getUserLastRunMessage());
+
+    jsonWriter.endObject().close();
+  }
+
+  /**
+   * Converts the query results retrieved from the datastore to json parsable by javascript
+   * into a DataTable object for use with a motion chart.
+   */
+  private void writeResultsToMotionChartJson(JsonWriter jsonWriter, Iterable<Entity> results)
+      throws IOException {
+    jsonWriter.name("data").beginObject();
+
+    // Write the header.
+    jsonWriter.name("cols").beginArray();
+    for (int i = 0; i < properties.length; i++) {
+      jsonWriter.beginObject()
+          .name("id").value(properties[i])
+          .name("label").value(labels[i])
+          .name("type").value(types[i])
+          .endObject();
+    }
+    jsonWriter.endArray();
+
+    // Write the data.
+    jsonWriter.name("rows").beginArray();
+    for (Entity entity : results) {
+      jsonWriter.beginObject().name("c").beginArray();
+      for (int i = 0; i < properties.length; i++) {
+        String value = "";
+        if (entity.getProperty(properties[i]) != null) {
+          value = String.valueOf(entity.getProperty(properties[i]));
+        }
+
+        jsonWriter.beginObject().name("v").value(value).endObject();
+      }
+      jsonWriter.endArray().endObject();
+    }
+    jsonWriter.endArray();
+
+    jsonWriter.endObject();
+  }
+}

bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/DatastoreUtils.java

+// Copyright 2011 Google Inc. All Rights Reserved.
+
+package com.google.api.client.sample.bigquery.appengine.dashboard;
+
+import com.google.api.services.bigquery.model.Bigqueryfield;
+import com.google.api.services.bigquery.model.Row;
+import com.google.api.services.bigquery.model.RowF;
+import com.google.appengine.api.datastore.DatastoreService;
+import com.google.appengine.api.datastore.DatastoreServiceFactory;
+import com.google.appengine.api.datastore.Entity;
+import com.google.appengine.api.datastore.EntityNotFoundException;
+import com.google.appengine.api.datastore.FetchOptions;
+import com.google.appengine.api.datastore.Key;
+import com.google.appengine.api.datastore.KeyFactory;
+import com.google.appengine.api.datastore.Query;
+import com.google.common.base.Preconditions;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Utility methods for inserting, accessing, and deleting data in the datastore.
+ *
+ * @author lparkinson@google.com (Laura Parkinson)
+ */
+public class DatastoreUtils {
+
+  public static final String FAILED = "FAILED";
+
+  private final Key userEntityKey;
+  private final String resultKind;
+  private final DatastoreService service;
+  private Entity userEntity;
+
+  public DatastoreUtils(String userId) {
+    userEntityKey = KeyFactory.createKey("User", userId);
+    service = DatastoreServiceFactory.getDatastoreService();
+    resultKind = userId + "Result";
+
+    try {
+      userEntity = service.get(userEntityKey);
+    } catch (EntityNotFoundException e) {
+      userEntity = null;
+    }
+  }
+
+  public boolean hasUserEntity() {
+    return userEntity != null;
+  }
+
+  private void createUserIfNull() {
+    if (userEntity == null) {
+      userEntity = new Entity(userEntityKey);
+    }
+  }
+
+  /**
+   * Updates the user entity with the message and status, creating it if necessary.
+   */
+  public void putUserInformation(String message, String status) {
+    createUserIfNull();
+    userEntity.setProperty("jobStatus", status);
+    userEntity.setProperty("message", message);
+    service.put(userEntity);
+  }
+
+  /**
+   * Updates the user entity with the current time, creating it if necessary.
+   */
+  public void updateSuccessfulQueryTimestamp() {
+    createUserIfNull();
+    userEntity.setProperty("timestamp", System.currentTimeMillis());
+    service.put(userEntity);
+  }
+
+  public String getUserJobStatus() {
+    return getUserEntityProperty("jobStatus");
+  }
+
+  public Boolean hasUserQueryFailed() {
+    return (FAILED).equalsIgnoreCase(getUserJobStatus());
+  }
+
+  public String getUserMessage() {
+    return getUserEntityProperty("message");
+  }
+
+  public String getUserLastRunMessage() {
+    String timestamp = getUserEntityProperty("timestamp");
+    if (timestamp == null) {
+      return "never";
+    } else {
+      SimpleDateFormat format = new SimpleDateFormat("k:mm:ss 'on' MMMM d, yyyy zzz");
+      Date date = new Date(Long.valueOf(timestamp));
+      return format.format(date);
+    }
+  }
+
+  private String getUserEntityProperty(String propertyName) {
+    if (userEntity != null && userEntity.hasProperty(propertyName)) {
+      return String.valueOf(userEntity.getProperty(propertyName));
+    } else {
+      return null;
+    }
+  }
+
+  public List<Entity> getResults() {
+    Query query = new Query(resultKind, userEntityKey);
+    FetchOptions options = FetchOptions.Builder.withChunkSize(2000);
+    return service.prepare(query).asList(options);
+  }
+
+  /**
+   * Removes any existing results for the user from the datastore.
+   */
+  public void deleteExistingResults() {
+    ArrayList<Key> keys = new ArrayList<Key>();
+    List<Entity> results = getResults();
+    for (Entity entity : results) {
+      keys.add(entity.getKey());
+    }
+    service.delete(keys);
+  }
+
+  /**
+   * Copies each row of the given data into an entity, then puts all the entities
+   * to the datastore with the user's entity as their ancestor.
+   */
+  public void copyQueryResultsToDatastore(List<Bigqueryfield> fields,
+      List<Row> rows) {
+    ArrayList<Entity> entities = new ArrayList<Entity>();
+    Iterator<Row> rowsIterator = rows.iterator();
+    while (rowsIterator.hasNext()) {
+      Entity entity = new Entity(resultKind, userEntityKey);
+
+      // Copy the row into the entity -- fields become properties.
+      Iterator<Bigqueryfield> fieldsIterator = fields.iterator();
+      Iterator<RowF> dataIterator = rowsIterator.next().getF().iterator();
+
+      Preconditions.checkState(fieldsIterator.hasNext() == dataIterator.hasNext());
+      while (fieldsIterator.hasNext() && dataIterator.hasNext()) {
+        Object value = dataIterator.next().getV();
+        String strValue = (value != null) ? String.valueOf(value) : null;
+        entity.setProperty(fieldsIterator.next().getName(), strValue);
+        Preconditions.checkState(fieldsIterator.hasNext() == dataIterator.hasNext());
+      }
+      entities.add(entity);
+    }
+    service.put(entities);
+  }
+}

bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/DeniedAuth.java

+// Copyright 2011 Google Inc. All Rights Reserved.
+
+package com.google.api.client.sample.bigquery.appengine.dashboard;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Servlet that shows a page when the user doesn't allow the dashboard to access
+ * his/her Bigquery data.
+ *
+ * @author lparkinson@google.com (Laura Parkinson)
+ */
+public class DeniedAuth extends HttpServlet {
+
+  @Override
+  protected void doGet(HttpServletRequest request, HttpServletResponse response)
+      throws IOException {
+    response.addHeader("Content-Type", "text/html");
+    response.getWriter().print("You don't want to try the sample?");
+    response.setStatus(200);
+  }
+}

bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/MainServlet.java

+// Copyright 2011 Google Inc. All Rights Reserved.
+
+package com.google.api.client.sample.bigquery.appengine.dashboard;
+
+import java.io.IOException;
+import java.util.logging.Logger;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author lparkinson@google.com (Laura Parkinson)
+ *
+ */
+public class MainServlet extends AuthServlet {
+
+  private static final Logger log = Logger.getLogger(MainServlet.class.getName());
+
+  /**
+   * This servlet responds to a GET request with a stencil page that will be filled
+   * with a chart and a message by client-side javascript.  Also, if no data exists
+   * in the datastore for the current user, it sends a query to retrieve it.
+   */
+  @Override
+  protected void doGet(HttpServletRequest request, HttpServletResponse response)
+      throws IOException {
+    String userId = getUserId();
+    DatastoreUtils datastoreUtils = new DatastoreUtils(userId);
+
+    printPage(response, datastoreUtils.getUserLastRunMessage());
+
+    // Try to get data if this user is unknown, or if their last try failed.
+    if (!datastoreUtils.hasUserEntity() || datastoreUtils.hasUserQueryFailed()) {
+      runQuery(request, response, userId, datastoreUtils);
+    }
+  }
+
+  private void runQuery(HttpServletRequest request, HttpServletResponse response,
+      String userId, DatastoreUtils datastoreUtils) {
+    // Clear the information from the last run for this user
+    datastoreUtils.putUserInformation("Beginning query...", null);
+
+    String message;
+    String status = DatastoreUtils.FAILED;
+
+    try {
+      // Begin a query.  A task is begun to wait for the results of the query,
+      // and when the query finishes, that task (see TaskServlet) takes care
+      // of copying the results to the datastore.
+      BigqueryUtils bigqueryUtils = new BigqueryUtils(userId, getHttpTransport(), getJsonFactory());
+      bigqueryUtils.beginQuery();
+      message = "Began running your query";
+      status = bigqueryUtils.getJobStatus();
+
+    } catch (SampleDashboardException ex) {
+      // If bad credentials were the problem, clear them and ask the user to refresh.
+      AuthUtils authUtils = new AuthUtils(userId, getHttpTransport(), getJsonFactory());
+      if (authUtils.handleUnauthorizedException(ex)) {
+        message = "There was a problem running the query with your credentials. Refresh, please!";
+      } else {
+        message = "Encountered an exception (" + ex.getStatusCode() + "): " + ex.getMessage();
+        log.severe(message);
+      }
+    }
+
+    datastoreUtils.putUserInformation(message, status);
+  }
+
+  /**
+   * A post to this servlet reruns the query for the logged-in user.
+   */
+  @Override
+  protected void doPost(HttpServletRequest request, HttpServletResponse response) {
+    String userId = getUserId();
+    DatastoreUtils datastoreUtils = new DatastoreUtils(userId);
+    runQuery(request, response, userId, datastoreUtils);
+  }
+
+  private void printPage(HttpServletResponse response, String lastRun)
+      throws IOException {
+    response.setContentType("text/html");
+    response.setCharacterEncoding("UTF-8");
+    response.getWriter().print("<!doctype html><html><head>"
+        + "<script type=\"text/javascript\" src=\"https://www.google.com/jsapi\"></script>"
+        + "<script type=\"text/javascript\" src=\"drawGraph.js\"></script>"
+        + "<title>Bigquery sample dashboard</title></head><body><div style=\"width:800px;\">"
+        + "<input type=\"button\" id=\"refresh\" value=\"Run query\" style=\"float:right;\"/>"
+        + "Query last run: <span id=\"lastRun\">" + lastRun + "</span></div><br/>"
+        + "<div id=\"message\">Checking the datastore for cached results...</div>"
+        + "<div id=\"visualization\"></div><br/><a href=\"#\" id=\"toggle\">"
+        + "Show query that generated these results</a><br/><div id=\"query\">"
+        + htmlify(BigqueryUtils.buildExampleQuery())
+        + "</div></body></html>");
+  }
+
+  private String htmlify(String s) {
+    s = s.replace("\n", "<br/>");
+    s = s.replace("\t", "&nbsp;&nbsp;&nbsp;&nbsp;");
+    s = s.replace(" ", "&nbsp;");
+    return s;
+  }
+}

bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/OAuth2Callback.java

+// Copyright 2011 Google Inc. All Rights Reserved.
+
+package com.google.api.client.sample.bigquery.appengine.dashboard;
+
+import com.google.api.client.extensions.appengine.auth.AbstractAppEngineCallbackServlet;
+import com.google.api.client.extensions.auth.helpers.ThreeLeggedFlow;
+import com.google.api.client.googleapis.extensions.auth.helpers.oauth2.draft10.GoogleOAuth2ThreeLeggedFlow;
+
+
+import javax.jdo.PersistenceManagerFactory;
+
+/**
+ * Holds information used in the authorization flow, such as which URL to redirect
+ * to on success/failure.
+ *
+ * @author lparkinson@google.com (Laura Parkinson)
+ */
+public class OAuth2Callback extends AbstractAppEngineCallbackServlet {
+
+  private static final long serialVersionUID = 1L;
+
+  @Override
+  protected Class<? extends ThreeLeggedFlow> getConcreteFlowType() {
+    return GoogleOAuth2ThreeLeggedFlow.class;
+  }
+
+  @Override
+  protected String getCompletionCodeQueryParam() {
+    return "code";
+  }
+
+  @Override
+  protected String getDeniedRedirectUrl() {
+    return "/denied";
+  }
+
+  @Override
+  protected String getSuccessRedirectUrl() {
+    return "/";
+  }
+
+  @Override
+  protected PersistenceManagerFactory getPersistenceManagerFactory() {
+    return AuthUtils.PM_FACTORY;
+  }
+}

bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/SampleDashboardException.java

+// Copyright 2011 Google Inc. All Rights Reserved.
+
+package com.google.api.client.sample.bigquery.appengine.dashboard;
+
+import org.apache.http.client.HttpResponseException;
+
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Exception to wrap an arbitrary exception as a HttpResponseException.
+ *
+ * @author lparkinson@google.com (Laura Parkinson)
+ */
+public class SampleDashboardException extends HttpResponseException {
+
+  public SampleDashboardException(int statusCode, String s) {
+    super(statusCode, s);
+  }
+
+  public SampleDashboardException(Exception ex) {
+    super(getStatusFromException(ex), getMessageFromException(ex));
+  }
+
+  private static String getMessageFromException(Exception ex) {
+    if (ex instanceof com.google.api.client.http.HttpResponseException) {
+      com.google.api.client.http.HttpResponseException hrex =
+          (com.google.api.client.http.HttpResponseException) ex;
+      return "The server encountered an exception: " + hrex.getResponse().getStatusMessage();
+    } else {
+      return "The server encountered an exception: " + ex.getMessage();
+    }
+  }
+
+  private static int getStatusFromException(Exception ex) {
+    if (ex instanceof com.google.api.client.http.HttpResponseException) {
+      com.google.api.client.http.HttpResponseException hrex =
+          (com.google.api.client.http.HttpResponseException) ex;
+      return hrex.getResponse().getStatusCode();
+    } else {
+      return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+    }
+  }
+}

bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/TaskServlet.java

+// Copyright 2011 Google Inc. All Rights Reserved.
+
+package com.google.api.client.sample.bigquery.appengine.dashboard;
+
+import com.google.api.client.extensions.appengine.http.urlfetch.UrlFetchTransport;
+import com.google.api.client.http.HttpTransport;
+import com.google.api.client.json.JsonFactory;
+import com.google.api.client.json.jackson.JacksonFactory;
+
+import java.util.logging.Logger;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * This servlet receives a post request when the task that was waiting for the
+ * query to finish comes out of the App Engine task queue.  It gets the status
+ * of the query from Bigquery and:
+ *   - copies the results to the datastore if the query has finished successfully
+ *   - enqueues another task to wait if the query is running/pending
+ *   - handles query failure
+ *   - clears the users's credentials and asks them to refresh if it catches a 401
+ *
+ * Note that because of the auth-constraint defined in web.xml, this can only be
+ * called by App Engine, and not by users.
+ *
+ * @author lparkinson@google.com (Laura Parkinson)
+ */
+public class TaskServlet extends HttpServlet {
+
+  private static final Logger log = Logger.getLogger(TaskServlet.class.getName());
+
+  private final HttpTransport transport = new UrlFetchTransport();
+  private final JsonFactory jsonFactory = new JacksonFactory();
+
+  @Override
+  protected void doPost(HttpServletRequest request, HttpServletResponse response) {
+    String userId = request.getParameter("userId");
+    String jobId = request.getParameter("jobId");
+
+    DatastoreUtils datastoreUtils = new DatastoreUtils(userId);
+    String message;
+    String status = DatastoreUtils.FAILED;
+
+    try {
+      BigqueryUtils bigqueryUtils = new BigqueryUtils(userId, transport, jsonFactory, jobId);
+
+      // If the job is done, handle it; otherwise, enqueue another task to wait for it.
+      if (bigqueryUtils.jobIsDone()) {
+        // Delete any previous results for this user.
+        datastoreUtils.deleteExistingResults();
+
+        // If the job succeeded, copy the results to the datastore.
+        if (bigqueryUtils.jobSucceeded()) {
+          datastoreUtils.copyQueryResultsToDatastore(bigqueryUtils.getSchemaFieldNames(),
+              bigqueryUtils.getTableData());
+
+          message = "Here are your results!";
+          status = bigqueryUtils.getJobStatus();
+
+          datastoreUtils.updateSuccessfulQueryTimestamp();
+        } else {
+          message = bigqueryUtils.getJobErrorMessage();
+        }
+      } else {
+        // If it's not done, keep waiting for it.
+        String jobStatus = bigqueryUtils.getJobStatus();
+        bigqueryUtils.enqueueWaitingTask();
+        message = "Waiting for the results of the query (" + jobStatus.toLowerCase() + ")";
+        status = jobStatus;
+      }
+    } catch (SampleDashboardException ex) {
+      // If bad credentials were the problem, clear them and ask the user to refresh.
+      AuthUtils authUtils = new AuthUtils(userId, transport, jsonFactory);
+      if (authUtils.handleUnauthorizedException(ex)) {
+        message = "Couldn't check on the query with your credentials. Refresh, please!";
+      } else {
+        message = "Encountered an exception (" + ex.getStatusCode() + "): " + ex.getMessage();
+        log.severe(message);
+      }
+    }
+
+    // Update the datastore with the new message and status.
+    datastoreUtils.putUserInformation(message, status);
+  }
+}

bigquery-appengine-sample/src/main/resources/META-INF/jdoconfig.xml

+<?xml version="1.0" encoding="utf-8"?>
+<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">
+
+   <persistence-manager-factory name="transactions-optional">
+       <property name="javax.jdo.PersistenceManagerFactoryClass"
+           value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>
+       <property name="javax.jdo.option.ConnectionURL" value="appengine"/>
+       <property name="javax.jdo.option.NontransactionalRead" value="true"/>
+       <property name="javax.jdo.option.NontransactionalWrite" value="true"/>
+       <property name="javax.jdo.option.RetainValues" value="true"/>
+       <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/>
+   </persistence-manager-factory>
+</jdoconfig>

bigquery-appengine-sample/src/main/webapp/WEB-INF/appengine-web.xml

+<?xml version="1.0" encoding="utf-8"?>
+<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
+  <application>google.com:bigquerysample</application>
+  <version>1</version>
+
+  <!--
+    By default, App Engine sends requests serially to a given web server.
+    To allow App Engine to send multiple requests in parallel specify:
+  -->
+
+  <threadsafe>true</threadsafe>
+
+  <!-- Configure serving/caching of GWT files -->
+  <static-files>
+    <include path="**" />
+
+    <!-- The following line requires App Engine 1.3.2 SDK -->
+    <include path="**.nocache.*" expiration="0s" />
+
+    <include path="**.cache.*" expiration="365d" />
+    <exclude path="**.gwt.rpc" />
+  </static-files>
+
+  <!-- Configure java.util.logging and Bigquery project -->
+  <system-properties>
+    <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
+    <!-- TODO Add your project ID here. -->
+    <property name="com.google.api.client.sample.bigquery.appengine.dashboard.projectId" value="google.com:bigquery-sample"/>
+  </system-properties>
+
+</appengine-web-app>

bigquery-appengine-sample/src/main/webapp/WEB-INF/logging.properties

+# A default java.util.logging configuration.
+# (All App Engine logging is through java.util.logging by default).
+#
+# To use this configuration, copy it into your application's WEB-INF
+# folder and add the following to your appengine-web.xml:
+#
+# <system-properties>
+#   <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
+# </system-properties>
+#
+
+# Set the default logging level for all loggers to WARNING
+.level = WARNING

bigquery-appengine-sample/src/main/webapp/WEB-INF/web.xml

+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
+              http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+         version="2.5"
+         xmlns="http://java.sun.com/xml/ns/javaee">
+
+  <!-- Servlets -->
+
+  <servlet>
+    <servlet-name>denied</servlet-name>
+    <servlet-class>com.google.api.client.sample.bigquery.appengine.dashboard.DeniedAuth</servlet-class>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>denied</servlet-name>
+    <url-pattern>/denied</url-pattern>
+  </servlet-mapping>
+
+
+  <servlet>
+    <servlet-name>oAuthCallback</servlet-name>
+    <servlet-class>com.google.api.client.sample.bigquery.appengine.dashboard.OAuth2Callback</servlet-class>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>oAuthCallback</servlet-name>
+    <url-pattern>/oauth2callback</url-pattern>
+  </servlet-mapping>
+
+  <servlet>
+    <servlet-name>taskServlet</servlet-name>
+    <servlet-class>com.google.api.client.sample.bigquery.appengine.dashboard.TaskServlet</servlet-class>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>taskServlet</servlet-name>
+    <url-pattern>/task</url-pattern>
+  </servlet-mapping>
+
+  <servlet>
+    <servlet-name>dataServlet</servlet-name>
+    <servlet-class>com.google.api.client.sample.bigquery.appengine.dashboard.DataServlet</servlet-class>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>dataServlet</servlet-name>
+    <url-pattern>/data</url-pattern>
+  </servlet-mapping>
+
+  <servlet>
+    <servlet-name>mainServlet</servlet-name>
+    <servlet-class>com.google.api.client.sample.bigquery.appengine.dashboard.MainServlet</servlet-class>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>mainServlet</servlet-name>
+    <url-pattern>/</url-pattern>
+  </servlet-mapping>
+
+  <security-constraint>
+    <web-resource-collection>
+      <web-resource-name>any</web-resource-name>
+      <url-pattern>/*</url-pattern>
+    </web-resource-collection>
+    <auth-constraint>
+      <role-name>*</role-name>
+    </auth-constraint>
+  </security-constraint>
+
+  <security-constraint>
+    <web-resource-collection>
+      <web-resource-name>any</web-resource-name>
+      <url-pattern>/task</url-pattern>
+    </web-resource-collection>
+    <auth-constraint>
+      <role-name>admin</role-name>
+    </auth-constraint>
+  </security-constraint>
+
+</web-app>

bigquery-appengine-sample/src/main/webapp/drawGraph.js

+// Copyright 2011 Google Inc. All Rights Reserved.
+
+/**
+ * @fileoverview This script posts to the data servlet with a request for data
+ * to display until either the servlet responds with data or responds that it
+ * failed. The servlet responds to each post with a message, which the script
+ * displays to the user and data if it exists, which the script draws as a
+ * motion chart.
+ *
+ * @author lparkinson@google.com (Laura Parkinson)
+ */
+
+// Load the motion chart package from the Visualization API and JQuery.
+google.load('visualization', '1', {packages: ['motionchart']});
+google.load('jquery', '1.6.4');
+
+// Set a callback to run when the Google Visualization API is loaded.
+google.setOnLoadCallback(doOnLoad);
+
+function doOnLoad() {
+  $('#query').hide();
+  $('#toggle').click(function() {
+    $('#query').toggle();
+  });
+
+  $('#refresh').click(function() {
+    $('#refresh').attr('disabled', 'disabled');
+    $('#message').html('Requesting that the query be rerun...');
+    $.post('/', function() {
+      setTimeout(postCheck, 2000);
+    });
+  });
+
+  postCheck();
+}
+
+function postCheck() {
+  $.post('/data', function(dataObject) {
+    $('#message').html(dataObject.message);
+
+    if (!dataObject.data && !dataObject.failed) {
+      setTimeout(postCheck, 2000);
+    } else {
+      $('#refresh').removeAttr('disabled');
+      if (dataObject.data) {
+        $('#lastRun').html(dataObject.lastRun);
+        
+        var width = 800;
+        var height = 400;
+        var viz = $('#visualization');
+        viz.css('width', width);
+        viz.css('height', height);
+
+        var dataTable = new google.visualization.DataTable(dataObject.data);
+        var motionchart = new google.visualization.MotionChart(viz[0]);
+        motionchart.draw(dataTable, {width: width, height: height});
+      }
+    }
+  }, 'json');
+}