Index: bigquery-appengine-sample/.checkstyle
===================================================================
new file mode 100644
--- /dev/null
+++ b/bigquery-appengine-sample/.checkstyle
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
Index: bigquery-appengine-sample/.classpath
===================================================================
new file mode 100644
--- /dev/null
+++ b/bigquery-appengine-sample/.classpath
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
Index: bigquery-appengine-sample/.project
===================================================================
new file mode 100644
--- /dev/null
+++ b/bigquery-appengine-sample/.project
@@ -0,0 +1,39 @@
+
+
+ dashboard
+ NO_M2ECLIPSE_SUPPORT: Project files created with the maven-eclipse-plugin are not supported in M2Eclipse.
+
+
+
+
+ com.google.appengine.eclipse.core.projectValidator
+
+
+
+
+ com.google.gdt.eclipse.core.webAppProjectValidator
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ com.google.appengine.eclipse.core.enhancerbuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.m2e.core.maven2Nature
+ org.eclipse.jdt.core.javanature
+ com.google.appengine.eclipse.core.gaeNature
+
+
Index: bigquery-appengine-sample/.settings/com.google.appengine.eclipse.core.prefs
===================================================================
new file mode 100644
--- /dev/null
+++ b/bigquery-appengine-sample/.settings/com.google.appengine.eclipse.core.prefs
@@ -0,0 +1,4 @@
+#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=
Index: bigquery-appengine-sample/.settings/com.google.gdt.eclipse.core.prefs
===================================================================
new file mode 100644
--- /dev/null
+++ b/bigquery-appengine-sample/.settings/com.google.gdt.eclipse.core.prefs
@@ -0,0 +1,5 @@
+#Fri Nov 04 10:12:20 PDT 2011
+eclipse.preferences.version=1
+jarsExcludedFromWebInfLib=
+warSrcDir=target/war
+warSrcDirIsOutput=true
Index: bigquery-appengine-sample/.settings/com.google.gwt.eclipse.core.prefs
===================================================================
new file mode 100644
--- /dev/null
+++ b/bigquery-appengine-sample/.settings/com.google.gwt.eclipse.core.prefs
@@ -0,0 +1,4 @@
+#Wed Nov 09 11:40:20 PST 2011
+eclipse.preferences.version=1
+entryPointModules=
+filesCopiedToWebInfLib=
Index: bigquery-appengine-sample/.settings/org.eclipse.core.resources.prefs
===================================================================
new file mode 100644
--- /dev/null
+++ b/bigquery-appengine-sample/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,4 @@
+#Fri Nov 11 13:26:55 PST 2011
+eclipse.preferences.version=1
+encoding//src/main/java=UTF-8
+encoding//src/main/resources=UTF-8
Index: bigquery-appengine-sample/.settings/org.eclipse.jdt.core.prefs
===================================================================
new file mode 100644
--- /dev/null
+++ b/bigquery-appengine-sample/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,6 @@
+#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
Index: bigquery-appengine-sample/.settings/org.eclipse.ltk.core.refactoring.prefs
===================================================================
new file mode 100644
--- /dev/null
+++ b/bigquery-appengine-sample/.settings/org.eclipse.ltk.core.refactoring.prefs
@@ -0,0 +1,3 @@
+#Wed Nov 09 11:44:08 PST 2011
+eclipse.preferences.version=1
+org.eclipse.ltk.core.refactoring.enable.project.refactoring.history=false
Index: bigquery-appengine-sample/.settings/org.eclipse.m2e.core.prefs
===================================================================
new file mode 100644
--- /dev/null
+++ b/bigquery-appengine-sample/.settings/org.eclipse.m2e.core.prefs
@@ -0,0 +1,5 @@
+#Fri Nov 11 13:26:41 PST 2011
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
Index: bigquery-appengine-sample/instructions.html
===================================================================
new file mode 100755
--- /dev/null
+++ b/bigquery-appengine-sample/instructions.html
@@ -0,0 +1,62 @@
+
+
Google Bigquery App Engine Dashboard Sample
+
+Instructions for the Google Bigquery App Engine Dashboard Sample
+
+Browse Source Code
+
+
+
+Checkout Instructions
+
+Prerequisites: install Java 6 , Google App Engine
+1.4.0 , Mercurial , and Maven . You may need
+to set your JAVA_HOME
.
+
+cd [someDirectory]
+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
+
+Setup Project in Eclipse 3.7
+
+Prerequisites: install Eclipse ,
+Google Plugin for Eclipse , and the
+Mercurial plugin .
+
+
+ Import bigquery-appengine-sample
project
+
+ File > Import...
+ Select "General > Existing Project into Workspace" and click
+ "Next"
+ Click "Browse" next to "Select root directory", find [someDirectory] /google-api-java-client-samples/bigquery-appengine-sample
+ and click "Next"
+ Click "Finish"
+
+
+ Settings
+
+ Add your Bigquery project ID as a system property to bigquery-appengine-sample/src/main/webapp/WEB-INF/appengine-web.xml
+ Add the client ID and client secret of your project to shared/shared-sample-appengine/src/main/resources/client_secrets.json
. You can find your client id and secret on your API console for your Bigquery project
+ Run mvn source:jar install
in shared/shared-sample-appengine/
+ Run mvn clean package
in bigquery-appengine-sample/
+
+
+ Run
+
+ Right-click on project bigquery-appengine-sample
+ Run As > Web Application
+
+
+
+
+
+
Index: bigquery-appengine-sample/pom.xml
===================================================================
new file mode 100755
--- /dev/null
+++ b/bigquery-appengine-sample/pom.xml
@@ -0,0 +1,105 @@
+
+
+ 4.0.0
+
+ com.google
+ google
+ 5
+
+ com.google.api.client
+ bigquery-appengine-sample
+ war
+ 1.0.0-SNAPSHOT
+ bigquery-appengine-sample
+
+
+
+ google-api-services
+ http://mavenrepo.google-api-java-client.googlecode.com/hg
+
+
+
+
+
+ maven-gae-plugin-repo
+ Maven Google App Engine Repository
+ http://maven-gae-plugin.googlecode.com/svn/repository/
+
+
+
+
+ v2-1.3.1-beta
+ 1.5.4
+ 1.6.0-beta
+ ${project.build.directory}/${project.build.finalName}
+ UTF-8
+
+
+
+ war
+ ${webappDirectory}/WEB-INF/classes
+
+
+
+ maven-compiler-plugin
+ 2.3.2
+
+ 1.5
+ 1.5
+
+
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+ 2.1.1
+
+
+ compile
+
+ exploded
+
+
+
+
+ ${webappDirectory}
+
+
+
+
+
+
+
+ net.kindleit
+ gae-runtime
+ ${gae.version}
+ pom
+
+
+ com.google.apis-samples
+ shared-sample-appengine
+ 1.1.0
+
+
+ com.google.api-client
+ google-api-client
+ ${google-api-client.version}
+
+
+ com.google.api-client
+ google-api-client-extensions
+ ${google-api-client.version}
+
+
+ com.google.apis
+ google-api-services-bigquery
+ ${bigquery.version}
+
+
+ net.sf.jsr107cache
+ jsr107cache
+ 1.1
+
+
+
Index: bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/AuthServlet.java
===================================================================
new file mode 100755
--- /dev/null
+++ b/bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/AuthServlet.java
@@ -0,0 +1,55 @@
+// 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();
+ }
+ }
+}
Index: bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/AuthUtils.java
===================================================================
new file mode 100755
--- /dev/null
+++ b/bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/AuthUtils.java
@@ -0,0 +1,126 @@
+// 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;
+ }
+}
Index: bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/BigqueryUtils.java
===================================================================
new file mode 100755
--- /dev/null
+++ b/bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/BigqueryUtils.java
@@ -0,0 +1,241 @@
+// 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() {
+ 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() {
+ 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 getSchemaFieldNames() throws SampleDashboardException {
+ if (job != null) {
+ final Tablereference tableReference =
+ job.getConfiguration().getQuery().getDestinationTable();
+
+ Table table = tryToDo(new Callable() {
+ 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 getTableData() throws SampleDashboardException {
+ if (job != null) {
+ final Tablereference tableReference =
+ job.getConfiguration().getQuery().getDestinationTable();
+
+ TableDataList tableDataList = tryToDo(new Callable() {
+ 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 tryToDo(Callable 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);
+ }
+ }
+}
Index: bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/DataServlet.java
===================================================================
new file mode 100755
--- /dev/null
+++ b/bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/DataServlet.java
@@ -0,0 +1,97 @@
+// 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 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 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();
+ }
+}
Index: bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/DatastoreUtils.java
===================================================================
new file mode 100755
--- /dev/null
+++ b/bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/DatastoreUtils.java
@@ -0,0 +1,154 @@
+// 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 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 keys = new ArrayList();
+ List 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 fields,
+ List rows) {
+ ArrayList entities = new ArrayList();
+ Iterator rowsIterator = rows.iterator();
+ while (rowsIterator.hasNext()) {
+ Entity entity = new Entity(resultKind, userEntityKey);
+
+ // Copy the row into the entity -- fields become properties.
+ Iterator fieldsIterator = fields.iterator();
+ Iterator 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);
+ }
+}
Index: bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/DeniedAuth.java
===================================================================
new file mode 100755
--- /dev/null
+++ b/bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/DeniedAuth.java
@@ -0,0 +1,26 @@
+// 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);
+ }
+}
Index: bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/MainServlet.java
===================================================================
new file mode 100755
--- /dev/null
+++ b/bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/MainServlet.java
@@ -0,0 +1,102 @@
+// 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(""
+ + ""
+ + ""
+ + "Bigquery sample dashboard "
+ + " "
+ + "Query last run: " + lastRun + "
"
+ + "Checking the datastore for cached results...
"
+ + "
"
+ + "Show query that generated these results "
+ + htmlify(BigqueryUtils.buildExampleQuery())
+ + "
");
+ }
+
+ private String htmlify(String s) {
+ s = s.replace("\n", " ");
+ s = s.replace("\t", " ");
+ s = s.replace(" ", " ");
+ return s;
+ }
+}
Index: bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/OAuth2Callback.java
===================================================================
new file mode 100755
--- /dev/null
+++ b/bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/OAuth2Callback.java
@@ -0,0 +1,46 @@
+// 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;
+ }
+}
Index: bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/SampleDashboardException.java
===================================================================
new file mode 100755
--- /dev/null
+++ b/bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/SampleDashboardException.java
@@ -0,0 +1,43 @@
+// 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;
+ }
+ }
+}
Index: bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/TaskServlet.java
===================================================================
new file mode 100755
--- /dev/null
+++ b/bigquery-appengine-sample/src/main/java/com/google/api/client/sample/bigquery/appengine/dashboard/TaskServlet.java
@@ -0,0 +1,87 @@
+// 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);
+ }
+}
Index: bigquery-appengine-sample/src/main/resources/META-INF/jdoconfig.xml
===================================================================
new file mode 100755
--- /dev/null
+++ b/bigquery-appengine-sample/src/main/resources/META-INF/jdoconfig.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
Index: bigquery-appengine-sample/src/main/webapp/WEB-INF/appengine-web.xml
===================================================================
new file mode 100755
--- /dev/null
+++ b/bigquery-appengine-sample/src/main/webapp/WEB-INF/appengine-web.xml
@@ -0,0 +1,31 @@
+
+
+ google.com:bigquerysample
+ 1
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: bigquery-appengine-sample/src/main/webapp/WEB-INF/logging.properties
===================================================================
new file mode 100755
--- /dev/null
+++ b/bigquery-appengine-sample/src/main/webapp/WEB-INF/logging.properties
@@ -0,0 +1,13 @@
+# 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:
+#
+#
+#
+#
+#
+
+# Set the default logging level for all loggers to WARNING
+.level = WARNING
Index: bigquery-appengine-sample/src/main/webapp/WEB-INF/web.xml
===================================================================
new file mode 100755
--- /dev/null
+++ b/bigquery-appengine-sample/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+ denied
+ com.google.api.client.sample.bigquery.appengine.dashboard.DeniedAuth
+
+
+
+ denied
+ /denied
+
+
+
+
+ oAuthCallback
+ com.google.api.client.sample.bigquery.appengine.dashboard.OAuth2Callback
+
+
+
+ oAuthCallback
+ /oauth2callback
+
+
+
+ taskServlet
+ com.google.api.client.sample.bigquery.appengine.dashboard.TaskServlet
+
+
+
+ taskServlet
+ /task
+
+
+
+ dataServlet
+ com.google.api.client.sample.bigquery.appengine.dashboard.DataServlet
+
+
+
+ dataServlet
+ /data
+
+
+
+ mainServlet
+ com.google.api.client.sample.bigquery.appengine.dashboard.MainServlet
+
+
+
+ mainServlet
+ /
+
+
+
+
+ any
+ /*
+
+
+ *
+
+
+
+
+
+ any
+ /task
+
+
+ admin
+
+
+
+
Index: bigquery-appengine-sample/src/main/webapp/drawGraph.js
===================================================================
new file mode 100755
--- /dev/null
+++ b/bigquery-appengine-sample/src/main/webapp/drawGraph.js
@@ -0,0 +1,60 @@
+// 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');
+}