App Engine Python SDK version 1.8.5

git-svn-id: http://googleappengine.googlecode.com/svn/trunk/python@384 80f5ef21-4148-0410-bacc-cfb02402ada8
diff --git a/RELEASE_NOTES b/RELEASE_NOTES
index 223eb00..6bfb08d 100644
--- a/RELEASE_NOTES
+++ b/RELEASE_NOTES
@@ -1,8 +1,44 @@
-Copyright 2008 Google Inc.
+Copyright 2013 Google Inc.
 All rights reserved.
 
 App Engine SDK - Release Notes
 
+Version 1.8.5
+
+All
+==============================
+- The Search API is now a GA feature.
+- Fixed an issue with the dev_appserver Datastore where UTF-8 validation did
+  not occur on string values.
+- Fixed an issue with the Admin Console Datastore Viewer throwing errors when
+  fetching UTF-8 kinds.
+    https://code.google.com/p/googleappengine/issues/detail?id=7570
+- Fixed an issue with the Datastore Admin not being able to backup to another
+  app.
+    https://code.google.com/p/googleappengine/issues/detail?id=9808
+- Fixed an issue where files that contain an '@' could not be deployed.
+    https://code.google.com/a/google.com/p/google-appengine-php-trusted-tester
+    /issues/detail?id=6
+
+Python
+==============================
+- Developers can now use pdb when debugging python applications in the
+  dev_appserver.
+    https://code.google.com/p/googleappengine/issues/detail?id=9012
+    https://code.google.com/p/googleappengine/issues/detail?id=9027
+
+PHP
+==============================
+- Modules are now supported for the PHP Runtime.
+- The Log Service API is now available.
+- The PHP interpreter has been upgraded from PHP 5.4.17 to PHP 5.4.19.
+- The /e modifier for mb_ereg_replace has been disabled.
+- The PHP interpreter has been switched to the JSON-C library.
+- The Mac OSX SDK now includes a PHP interpreter, installing PHP separately is
+  no longer required.
+- The Windows SDK now includes a PHP interpreter, installing PHP separately is
+  no longer required.
+
 Version 1.8.4
 
 All
diff --git a/VERSION b/VERSION
index 8effc62..82756fd 100644
--- a/VERSION
+++ b/VERSION
@@ -1,5 +1,5 @@
-release: "1.8.4"
-timestamp: 1375825037
+release: "1.8.5"
+timestamp: 1378244055
 api_versions: ['1']
 supported_api_versions:
   python:
diff --git a/api_server.py b/api_server.py
index 661f26e..fa956c5 100644
--- a/api_server.py
+++ b/api_server.py
@@ -114,6 +114,16 @@
 API_SERVER_EXTRA_PATH_SCRIPTS = 'api_server'
 
 
+
+
+ENDPOINTSCFG_EXTRA_PATHS = [
+  os.path.join(DIR_PATH, 'lib', 'cherrypy'),
+  os.path.join(DIR_PATH, 'lib', 'concurrent'),
+  os.path.join(DIR_PATH, 'lib', 'endpoints-1.0'),
+]
+ENDPOINTSCFG_EXTRA_PATH_SCRIPTS = 'endpointscfg'
+
+
 OAUTH_CLIENT_EXTRA_PATHS = [
   os.path.join(DIR_PATH, 'lib', 'google-api-python-client'),
   os.path.join(DIR_PATH, 'lib', 'httplib2'),
@@ -161,6 +171,8 @@
     extra_extra_paths = GOOGLE_SQL_EXTRA_PATHS
   elif re.match(API_SERVER_EXTRA_PATH_SCRIPTS, script_name):
     extra_extra_paths = API_SERVER_EXTRA_PATHS
+  elif re.match(ENDPOINTSCFG_EXTRA_PATH_SCRIPTS, script_name):
+    extra_extra_paths = ENDPOINTSCFG_EXTRA_PATHS
   else:
     extra_extra_paths = []
   fix_sys_path(extra_extra_paths)
diff --git a/appcfg.py b/appcfg.py
index 661f26e..fa956c5 100644
--- a/appcfg.py
+++ b/appcfg.py
@@ -114,6 +114,16 @@
 API_SERVER_EXTRA_PATH_SCRIPTS = 'api_server'
 
 
+
+
+ENDPOINTSCFG_EXTRA_PATHS = [
+  os.path.join(DIR_PATH, 'lib', 'cherrypy'),
+  os.path.join(DIR_PATH, 'lib', 'concurrent'),
+  os.path.join(DIR_PATH, 'lib', 'endpoints-1.0'),
+]
+ENDPOINTSCFG_EXTRA_PATH_SCRIPTS = 'endpointscfg'
+
+
 OAUTH_CLIENT_EXTRA_PATHS = [
   os.path.join(DIR_PATH, 'lib', 'google-api-python-client'),
   os.path.join(DIR_PATH, 'lib', 'httplib2'),
@@ -161,6 +171,8 @@
     extra_extra_paths = GOOGLE_SQL_EXTRA_PATHS
   elif re.match(API_SERVER_EXTRA_PATH_SCRIPTS, script_name):
     extra_extra_paths = API_SERVER_EXTRA_PATHS
+  elif re.match(ENDPOINTSCFG_EXTRA_PATH_SCRIPTS, script_name):
+    extra_extra_paths = ENDPOINTSCFG_EXTRA_PATHS
   else:
     extra_extra_paths = []
   fix_sys_path(extra_extra_paths)
diff --git a/backends_conversion.py b/backends_conversion.py
index 661f26e..fa956c5 100644
--- a/backends_conversion.py
+++ b/backends_conversion.py
@@ -114,6 +114,16 @@
 API_SERVER_EXTRA_PATH_SCRIPTS = 'api_server'
 
 
+
+
+ENDPOINTSCFG_EXTRA_PATHS = [
+  os.path.join(DIR_PATH, 'lib', 'cherrypy'),
+  os.path.join(DIR_PATH, 'lib', 'concurrent'),
+  os.path.join(DIR_PATH, 'lib', 'endpoints-1.0'),
+]
+ENDPOINTSCFG_EXTRA_PATH_SCRIPTS = 'endpointscfg'
+
+
 OAUTH_CLIENT_EXTRA_PATHS = [
   os.path.join(DIR_PATH, 'lib', 'google-api-python-client'),
   os.path.join(DIR_PATH, 'lib', 'httplib2'),
@@ -161,6 +171,8 @@
     extra_extra_paths = GOOGLE_SQL_EXTRA_PATHS
   elif re.match(API_SERVER_EXTRA_PATH_SCRIPTS, script_name):
     extra_extra_paths = API_SERVER_EXTRA_PATHS
+  elif re.match(ENDPOINTSCFG_EXTRA_PATH_SCRIPTS, script_name):
+    extra_extra_paths = ENDPOINTSCFG_EXTRA_PATHS
   else:
     extra_extra_paths = []
   fix_sys_path(extra_extra_paths)
diff --git a/bulkload_client.py b/bulkload_client.py
index 661f26e..fa956c5 100644
--- a/bulkload_client.py
+++ b/bulkload_client.py
@@ -114,6 +114,16 @@
 API_SERVER_EXTRA_PATH_SCRIPTS = 'api_server'
 
 
+
+
+ENDPOINTSCFG_EXTRA_PATHS = [
+  os.path.join(DIR_PATH, 'lib', 'cherrypy'),
+  os.path.join(DIR_PATH, 'lib', 'concurrent'),
+  os.path.join(DIR_PATH, 'lib', 'endpoints-1.0'),
+]
+ENDPOINTSCFG_EXTRA_PATH_SCRIPTS = 'endpointscfg'
+
+
 OAUTH_CLIENT_EXTRA_PATHS = [
   os.path.join(DIR_PATH, 'lib', 'google-api-python-client'),
   os.path.join(DIR_PATH, 'lib', 'httplib2'),
@@ -161,6 +171,8 @@
     extra_extra_paths = GOOGLE_SQL_EXTRA_PATHS
   elif re.match(API_SERVER_EXTRA_PATH_SCRIPTS, script_name):
     extra_extra_paths = API_SERVER_EXTRA_PATHS
+  elif re.match(ENDPOINTSCFG_EXTRA_PATH_SCRIPTS, script_name):
+    extra_extra_paths = ENDPOINTSCFG_EXTRA_PATHS
   else:
     extra_extra_paths = []
   fix_sys_path(extra_extra_paths)
diff --git a/bulkloader.py b/bulkloader.py
index 661f26e..fa956c5 100644
--- a/bulkloader.py
+++ b/bulkloader.py
@@ -114,6 +114,16 @@
 API_SERVER_EXTRA_PATH_SCRIPTS = 'api_server'
 
 
+
+
+ENDPOINTSCFG_EXTRA_PATHS = [
+  os.path.join(DIR_PATH, 'lib', 'cherrypy'),
+  os.path.join(DIR_PATH, 'lib', 'concurrent'),
+  os.path.join(DIR_PATH, 'lib', 'endpoints-1.0'),
+]
+ENDPOINTSCFG_EXTRA_PATH_SCRIPTS = 'endpointscfg'
+
+
 OAUTH_CLIENT_EXTRA_PATHS = [
   os.path.join(DIR_PATH, 'lib', 'google-api-python-client'),
   os.path.join(DIR_PATH, 'lib', 'httplib2'),
@@ -161,6 +171,8 @@
     extra_extra_paths = GOOGLE_SQL_EXTRA_PATHS
   elif re.match(API_SERVER_EXTRA_PATH_SCRIPTS, script_name):
     extra_extra_paths = API_SERVER_EXTRA_PATHS
+  elif re.match(ENDPOINTSCFG_EXTRA_PATH_SCRIPTS, script_name):
+    extra_extra_paths = ENDPOINTSCFG_EXTRA_PATHS
   else:
     extra_extra_paths = []
   fix_sys_path(extra_extra_paths)
diff --git a/demos/php/guestbook/app.yaml b/demos/php/guestbook/app.yaml
new file mode 100644
index 0000000..42e8d26
--- /dev/null
+++ b/demos/php/guestbook/app.yaml
@@ -0,0 +1,19 @@
+application: guestbook
+version: 1
+runtime: php
+api_version: 1
+threadsafe: no
+
+handlers:
+- url: .*
+  script: main.php
+
+env_variables:
+ DEVELOPMENT_DB_HOST: ''
+ DEVELOPMENT_DB_USERNAME: 'root'
+ DEVELOPMENT_DB_PASSWORD: ''
+ DEVELOPMENT_DB_NAME: 'guestbook'
+ PRODUCTION_CLOUD_SQL_INSTANCE: '/cloudsql/guestbook:my-cloudsql-instance'
+ PRODUCTION_DB_USERNAME: ''
+ PRODUCTION_DB_PASSWORD: ''
+ PRODUCTION_DB_NAME: 'guestbook'
\ No newline at end of file
diff --git a/demos/php/guestbook/main.php b/demos/php/guestbook/main.php
new file mode 100644
index 0000000..d494abc
--- /dev/null
+++ b/demos/php/guestbook/main.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+require_once 'google/appengine/api/users/User.php';
+  require_once 'google/appengine/api/users/UserService.php';
+
+  use google\appengine\api\users\User;
+  use google\appengine\api\users\UserService;
+
+  $greeting_schema = <<<SCHEMA
+CREATE TABLE IF NOT EXISTS greeting (
+    id INT NOT NULL AUTO_INCREMENT,
+    author VARCHAR(100) NOT NULL,
+    content TEXT NOT NULL,
+    PRIMARY KEY (id)
+)
+SCHEMA;
+
+  if (strpos(getenv('SERVER_SOFTWARE'), 'Development') === false) {
+    $conn = mysqli_connect(null,
+                           getenv('PRODUCTION_DB_USERNAME'),
+                           getenv('PRODUCTION_DB_PASSWORD'),
+                           null,
+                           null,
+                           getenv('PRODUCTION_CLOUD_SQL_INSTANCE'));
+    $db = getenv('PRODUCTION_DB_NAME');
+  } else {
+    $conn = mysqli_connect(getenv('DEVELOPMENT_DB_HOST'), 
+                           getenv('DEVELOPMENT_DB_USERNAME'),
+                           getenv('DEVELOPMENT_DB_PASSWORD'));
+    $db = getenv('DEVELOPMENT_DB_NAME');
+  }
+
+  if ($conn->connect_error) {
+    die("Could not connect to database: $conn->connect_error " .
+        "[$conn->connect_errno]");
+  }
+
+  if ($conn->query("CREATE DATABASE IF NOT EXISTS $db") === FALSE) {
+    die("Could not create database: $conn->error [$conn->errno]");
+  }
+
+  if ($conn->select_db($db) === FALSE) {
+    die("Could not select database: $conn->error [$conn->errno]");
+  }
+
+  if ($conn->query($greeting_schema) === FALSE) {
+    die("Could not create tables: $conn->error [$conn->errno]");
+  }
+
+  $user = UserService::getCurrentUser();
+  if($user) {
+    $url = UserService::createLogoutURL('', "google.com");
+    $url_linktext = "Logout";
+    $author = $user->getNickname();
+  }
+  else {
+    $url = UserService::createLoginURL('');
+    $url_linktext = "Login";
+    $author = "";
+  }
+
+  if(isset($_POST['content'])) {
+    $stmt = $conn->prepare(
+        "INSERT INTO greeting (author, content) VALUES (?, ?)");
+    if ($stmt->bind_param('ss', $author, $_POST['content']) === FALSE) {
+      die("Could not bind prepared statement");
+    }
+
+    if ($stmt->execute() === FALSE) {
+      die("Could not execute prepared statement");
+    }
+   $stmt->close();
+   header("Location: /");
+   exit();
+  }
+?>
+<html>
+  <body>
+    <?php
+      $stmt = $conn->prepare(
+          "SELECT author, content FROM greeting ORDER BY id DESC LIMIT 10");
+      if ($stmt->execute() === FALSE) {
+        die("Could not execute prepared statement");
+      }
+      $stmt->bind_result($author, $content);
+      while ($stmt->fetch()) {
+          if($author) {
+            echo '<b>' . htmlentities($author) . '</b> wrote:';
+          } else {
+            echo 'An anonymous person wrote:';
+          }
+          echo '<blockquote>' . htmlentities($content) . '</blockquote>';
+      }
+      $stmt->close();
+    ?>
+
+    <form method="post" name="guestbook_form">
+      <div><textarea name="content" rows="3" cols="60"></textarea></div>
+      <div><input type="submit" value="Sign Guestbook"></div>
+    </form>
+
+    <a href="<?php echo $url; ?>"><?php echo $url_linktext; ?></a>
+  </body>
+</html>
\ No newline at end of file
diff --git a/download_appstats.py b/download_appstats.py
index 661f26e..fa956c5 100644
--- a/download_appstats.py
+++ b/download_appstats.py
@@ -114,6 +114,16 @@
 API_SERVER_EXTRA_PATH_SCRIPTS = 'api_server'
 
 
+
+
+ENDPOINTSCFG_EXTRA_PATHS = [
+  os.path.join(DIR_PATH, 'lib', 'cherrypy'),
+  os.path.join(DIR_PATH, 'lib', 'concurrent'),
+  os.path.join(DIR_PATH, 'lib', 'endpoints-1.0'),
+]
+ENDPOINTSCFG_EXTRA_PATH_SCRIPTS = 'endpointscfg'
+
+
 OAUTH_CLIENT_EXTRA_PATHS = [
   os.path.join(DIR_PATH, 'lib', 'google-api-python-client'),
   os.path.join(DIR_PATH, 'lib', 'httplib2'),
@@ -161,6 +171,8 @@
     extra_extra_paths = GOOGLE_SQL_EXTRA_PATHS
   elif re.match(API_SERVER_EXTRA_PATH_SCRIPTS, script_name):
     extra_extra_paths = API_SERVER_EXTRA_PATHS
+  elif re.match(ENDPOINTSCFG_EXTRA_PATH_SCRIPTS, script_name):
+    extra_extra_paths = ENDPOINTSCFG_EXTRA_PATHS
   else:
     extra_extra_paths = []
   fix_sys_path(extra_extra_paths)
diff --git a/endpointscfg.py b/endpointscfg.py
index 661f26e..fa956c5 100644
--- a/endpointscfg.py
+++ b/endpointscfg.py
@@ -114,6 +114,16 @@
 API_SERVER_EXTRA_PATH_SCRIPTS = 'api_server'
 
 
+
+
+ENDPOINTSCFG_EXTRA_PATHS = [
+  os.path.join(DIR_PATH, 'lib', 'cherrypy'),
+  os.path.join(DIR_PATH, 'lib', 'concurrent'),
+  os.path.join(DIR_PATH, 'lib', 'endpoints-1.0'),
+]
+ENDPOINTSCFG_EXTRA_PATH_SCRIPTS = 'endpointscfg'
+
+
 OAUTH_CLIENT_EXTRA_PATHS = [
   os.path.join(DIR_PATH, 'lib', 'google-api-python-client'),
   os.path.join(DIR_PATH, 'lib', 'httplib2'),
@@ -161,6 +171,8 @@
     extra_extra_paths = GOOGLE_SQL_EXTRA_PATHS
   elif re.match(API_SERVER_EXTRA_PATH_SCRIPTS, script_name):
     extra_extra_paths = API_SERVER_EXTRA_PATHS
+  elif re.match(ENDPOINTSCFG_EXTRA_PATH_SCRIPTS, script_name):
+    extra_extra_paths = ENDPOINTSCFG_EXTRA_PATHS
   else:
     extra_extra_paths = []
   fix_sys_path(extra_extra_paths)
diff --git a/gen_protorpc.py b/gen_protorpc.py
index 661f26e..fa956c5 100644
--- a/gen_protorpc.py
+++ b/gen_protorpc.py
@@ -114,6 +114,16 @@
 API_SERVER_EXTRA_PATH_SCRIPTS = 'api_server'
 
 
+
+
+ENDPOINTSCFG_EXTRA_PATHS = [
+  os.path.join(DIR_PATH, 'lib', 'cherrypy'),
+  os.path.join(DIR_PATH, 'lib', 'concurrent'),
+  os.path.join(DIR_PATH, 'lib', 'endpoints-1.0'),
+]
+ENDPOINTSCFG_EXTRA_PATH_SCRIPTS = 'endpointscfg'
+
+
 OAUTH_CLIENT_EXTRA_PATHS = [
   os.path.join(DIR_PATH, 'lib', 'google-api-python-client'),
   os.path.join(DIR_PATH, 'lib', 'httplib2'),
@@ -161,6 +171,8 @@
     extra_extra_paths = GOOGLE_SQL_EXTRA_PATHS
   elif re.match(API_SERVER_EXTRA_PATH_SCRIPTS, script_name):
     extra_extra_paths = API_SERVER_EXTRA_PATHS
+  elif re.match(ENDPOINTSCFG_EXTRA_PATH_SCRIPTS, script_name):
+    extra_extra_paths = ENDPOINTSCFG_EXTRA_PATHS
   else:
     extra_extra_paths = []
   fix_sys_path(extra_extra_paths)
diff --git a/google/appengine/api/appinfo.py b/google/appengine/api/appinfo.py
index 18732d7..96ca58c 100644
--- a/google/appengine/api/appinfo.py
+++ b/google/appengine/api/appinfo.py
@@ -323,6 +323,11 @@
         experimental_versions=['1.5'],
         ),
     _VersionedLibrary(
+        'endpoints',
+        'https://developers.google.com/appengine/docs/python/endpoints/',
+        'Libraries for building APIs in an App Engine application.',
+        ['1.0']),
+    _VersionedLibrary(
         'jinja2',
         'http://jinja.pocoo.org/docs/',
         'A modern and designer friendly templating language for Python.',
diff --git a/google/appengine/api/blobstore/blobstore.py b/google/appengine/api/blobstore/blobstore.py
index 9887b0d..1c869e9 100644
--- a/google/appengine/api/blobstore/blobstore.py
+++ b/google/appengine/api/blobstore/blobstore.py
@@ -461,19 +461,14 @@
 def create_gs_key(filename, rpc=None):
   """Create an encoded key for a Google Storage file.
 
-  The created blob key will include short lived access token using the
-  application's service account for authorization.
-
-  This blob key should not be stored permanently as the access token will
-  expire.
+  It is safe to persist this key for future use.
 
   Args:
     filename: The filename of the google storage object to create the key for.
     rpc: Optional UserRPC object.
 
   Returns:
-    An encrypted blob key object that also contains a short term access token
-      that represents the application's service account.
+    An encrypted blob key string.
   """
   rpc = create_gs_key_async(filename, rpc)
   return rpc.get_result()
@@ -482,11 +477,7 @@
 def create_gs_key_async(filename, rpc=None):
   """Create an encoded key for a google storage file - async version.
 
-  The created blob key will include short lived access token using the
-  application's service account for authorization.
-
-  This blob key should not be stored permanently as the access token will
-  expire.
+  It is safe to persist this key for future use.
 
   Args:
     filename: The filename of the google storage object to create the
@@ -494,7 +485,7 @@
     rpc: Optional UserRPC object.
 
   Returns:
-    A UserRPC whose result will be a str as returned by create_gs_key.
+    A UserRPC whose result will be a string as returned by create_gs_key.
 
   Raises:
     TypeError: If filename is not a string.
diff --git a/google/appengine/api/croninfo.py b/google/appengine/api/croninfo.py
index d7a051a..22d228c 100644
--- a/google/appengine/api/croninfo.py
+++ b/google/appengine/api/croninfo.py
@@ -138,6 +138,7 @@
 class CronInfoExternal(validation.Validated):
   """CronInfoExternal describes all cron entries for an application."""
   ATTRIBUTES = {
+      appinfo.APPLICATION: validation.Optional(appinfo.APPLICATION_RE_STRING),
       CRON: validation.Optional(validation.Repeated(CronEntry))
   }
 
diff --git a/google/appengine/api/datastore.py b/google/appengine/api/datastore.py
index 9bc0cc6..55e4712 100644
--- a/google/appengine/api/datastore.py
+++ b/google/appengine/api/datastore.py
@@ -348,11 +348,11 @@
 
   def pb_to_index(self, pb):
     index_def = pb.definition()
-    properties = [(property.name(),
+    properties = [(property.name().decode('utf-8'),
           DatastoreAdapter.index_direction_mappings.get(property.direction()))
           for property in index_def.property_list()]
-    index = Index(pb.id(), index_def.entity_type(), index_def.ancestor(),
-                  properties)
+    index = Index(pb.id(), index_def.entity_type().decode('utf-8'),
+                  index_def.ancestor(), properties)
     state = DatastoreAdapter.index_state_mappings.get(pb.state())
     return index, state
 
diff --git a/google/appengine/api/dosinfo.py b/google/appengine/api/dosinfo.py
index 09da279..22637ce 100644
--- a/google/appengine/api/dosinfo.py
+++ b/google/appengine/api/dosinfo.py
@@ -33,6 +33,7 @@
 import google
 import ipaddr
 
+from google.appengine.api import appinfo
 from google.appengine.api import validation
 from google.appengine.api import yaml_builder
 from google.appengine.api import yaml_listener
@@ -86,6 +87,7 @@
 class DosInfoExternal(validation.Validated):
   """Describes the format of a dos.yaml file."""
   ATTRIBUTES = {
+      appinfo.APPLICATION: validation.Optional(appinfo.APPLICATION_RE_STRING),
       BLACKLIST: validation.Optional(validation.Repeated(BlacklistEntry)),
   }
 
diff --git a/google/appengine/api/mail.py b/google/appengine/api/mail.py
index c5020f8..e5b7395 100644
--- a/google/appengine/api/mail.py
+++ b/google/appengine/api/mail.py
@@ -476,6 +476,19 @@
     return 'utf-8'
 
 
+def _I18nHeader(text):
+  """Creates a header properly encoded even with unicode content.
+
+  Args:
+    text: a string (str) that is either a us-ascii string or a unicode that was
+        encoded in utf-8.
+  Returns:
+    email.header.Header
+  """
+  charset = _GuessCharset(text)
+  return email.header.Header(text, charset, maxlinelen=float('inf'))
+
+
 def mail_message_to_mime_message(protocol_message):
   """Generate a MIMEMultitype message from protocol buffer.
 
@@ -527,18 +540,18 @@
 
 
   if protocol_message.to_size():
-    result['To'] = ', '.join(protocol_message.to_list())
+    result['To'] = _I18nHeader(', '.join(protocol_message.to_list()))
   if protocol_message.cc_size():
-    result['Cc'] = ', '.join(protocol_message.cc_list())
+    result['Cc'] = _I18nHeader(', '.join(protocol_message.cc_list()))
   if protocol_message.bcc_size():
-    result['Bcc'] = ', '.join(protocol_message.bcc_list())
+    result['Bcc'] = _I18nHeader(', '.join(protocol_message.bcc_list()))
 
-  result['From'] = protocol_message.sender()
-  result['Reply-To'] = protocol_message.replyto()
-  result['Subject'] = protocol_message.subject()
+  result['From'] = _I18nHeader(protocol_message.sender())
+  result['Reply-To'] = _I18nHeader(protocol_message.replyto())
+  result['Subject'] = _I18nHeader(protocol_message.subject())
 
   for header in protocol_message.header_list():
-    result[header.name()] = header.value()
+    result[header.name()] = _I18nHeader(header.value())
 
   return result
 
diff --git a/google/appengine/api/mail_stub.py b/google/appengine/api/mail_stub.py
index e8df17d..7c42434 100644
--- a/google/appengine/api/mail_stub.py
+++ b/google/appengine/api/mail_stub.py
@@ -48,9 +48,6 @@
 class MailServiceStub(apiproxy_stub.APIProxyStub):
   """Python only mail service stub.
 
-  This stub does not actually attempt to send email.  instead it merely logs
-  a description of the email to the developers console.
-
   Args:
     host: Host of SMTP server to use.  Blank disables sending SMTP.
     port: Port of SMTP server to use.
@@ -230,7 +227,7 @@
       for to in ('To', 'Cc', 'Bcc'):
         if mime_message[to]:
           tos.extend("'%s'" % addr.strip().replace("'", r"'\''")
-                     for addr in mime_message[to].split(','))
+                     for addr in unicode(mime_message[to]).split(','))
 
       command = '%s %s' % (sendmail_command, ' '.join(tos))
 
diff --git a/google/appengine/api/queueinfo.py b/google/appengine/api/queueinfo.py
index 1fff260..06b89c2 100644
--- a/google/appengine/api/queueinfo.py
+++ b/google/appengine/api/queueinfo.py
@@ -134,6 +134,7 @@
 
 
 
+from google.appengine.api import appinfo
 from google.appengine.api import validation
 from google.appengine.api import yaml_builder
 from google.appengine.api import yaml_listener
@@ -223,6 +224,7 @@
 class QueueInfoExternal(validation.Validated):
   """QueueInfoExternal describes all queue entries for an application."""
   ATTRIBUTES = {
+      appinfo.APPLICATION: validation.Optional(appinfo.APPLICATION_RE_STRING),
       TOTAL_STORAGE_LIMIT: validation.Optional(_TOTAL_STORAGE_LIMIT_REGEX),
       QUEUE: validation.Optional(validation.Repeated(QueueEntry)),
   }
diff --git a/google/appengine/datastore/datastore_pb.py b/google/appengine/datastore/datastore_pb.py
index 049866a..8075e7c 100644
--- a/google/appengine/datastore/datastore_pb.py
+++ b/google/appengine/datastore/datastore_pb.py
@@ -3523,6 +3523,8 @@
   entity_write_bytes_ = 0
   has_commitcost_ = 0
   commitcost_ = None
+  has_approximate_storage_delta_ = 0
+  approximate_storage_delta_ = 0
 
   def __init__(self, contents=None):
     self.lazy_init_lock_ = thread.allocate_lock()
@@ -3599,6 +3601,19 @@
 
   def has_commitcost(self): return self.has_commitcost_
 
+  def approximate_storage_delta(self): return self.approximate_storage_delta_
+
+  def set_approximate_storage_delta(self, x):
+    self.has_approximate_storage_delta_ = 1
+    self.approximate_storage_delta_ = x
+
+  def clear_approximate_storage_delta(self):
+    if self.has_approximate_storage_delta_:
+      self.has_approximate_storage_delta_ = 0
+      self.approximate_storage_delta_ = 0
+
+  def has_approximate_storage_delta(self): return self.has_approximate_storage_delta_
+
 
   def MergeFrom(self, x):
     assert x is not self
@@ -3607,6 +3622,7 @@
     if (x.has_entity_writes()): self.set_entity_writes(x.entity_writes())
     if (x.has_entity_write_bytes()): self.set_entity_write_bytes(x.entity_write_bytes())
     if (x.has_commitcost()): self.mutable_commitcost().MergeFrom(x.commitcost())
+    if (x.has_approximate_storage_delta()): self.set_approximate_storage_delta(x.approximate_storage_delta())
 
   def Equals(self, x):
     if x is self: return 1
@@ -3620,6 +3636,8 @@
     if self.has_entity_write_bytes_ and self.entity_write_bytes_ != x.entity_write_bytes_: return 0
     if self.has_commitcost_ != x.has_commitcost_: return 0
     if self.has_commitcost_ and self.commitcost_ != x.commitcost_: return 0
+    if self.has_approximate_storage_delta_ != x.has_approximate_storage_delta_: return 0
+    if self.has_approximate_storage_delta_ and self.approximate_storage_delta_ != x.approximate_storage_delta_: return 0
     return 1
 
   def IsInitialized(self, debug_strs=None):
@@ -3634,6 +3652,7 @@
     if (self.has_entity_writes_): n += 1 + self.lengthVarInt64(self.entity_writes_)
     if (self.has_entity_write_bytes_): n += 1 + self.lengthVarInt64(self.entity_write_bytes_)
     if (self.has_commitcost_): n += 2 + self.commitcost_.ByteSize()
+    if (self.has_approximate_storage_delta_): n += 1 + self.lengthVarInt64(self.approximate_storage_delta_)
     return n
 
   def ByteSizePartial(self):
@@ -3643,6 +3662,7 @@
     if (self.has_entity_writes_): n += 1 + self.lengthVarInt64(self.entity_writes_)
     if (self.has_entity_write_bytes_): n += 1 + self.lengthVarInt64(self.entity_write_bytes_)
     if (self.has_commitcost_): n += 2 + self.commitcost_.ByteSizePartial()
+    if (self.has_approximate_storage_delta_): n += 1 + self.lengthVarInt64(self.approximate_storage_delta_)
     return n
 
   def Clear(self):
@@ -3651,6 +3671,7 @@
     self.clear_entity_writes()
     self.clear_entity_write_bytes()
     self.clear_commitcost()
+    self.clear_approximate_storage_delta()
 
   def OutputUnchecked(self, out):
     if (self.has_index_writes_):
@@ -3669,6 +3690,9 @@
       out.putVarInt32(43)
       self.commitcost_.OutputUnchecked(out)
       out.putVarInt32(44)
+    if (self.has_approximate_storage_delta_):
+      out.putVarInt32(64)
+      out.putVarInt32(self.approximate_storage_delta_)
 
   def OutputPartial(self, out):
     if (self.has_index_writes_):
@@ -3687,6 +3711,9 @@
       out.putVarInt32(43)
       self.commitcost_.OutputPartial(out)
       out.putVarInt32(44)
+    if (self.has_approximate_storage_delta_):
+      out.putVarInt32(64)
+      out.putVarInt32(self.approximate_storage_delta_)
 
   def TryMerge(self, d):
     while d.avail() > 0:
@@ -3706,6 +3733,9 @@
       if tt == 43:
         self.mutable_commitcost().TryMerge(d)
         continue
+      if tt == 64:
+        self.set_approximate_storage_delta(d.getVarInt32())
+        continue
 
 
       if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError
@@ -3722,6 +3752,7 @@
       res+=prefix+"CommitCost {\n"
       res+=self.commitcost_.__str__(prefix + "  ", printElemNumber)
       res+=prefix+"}\n"
+    if self.has_approximate_storage_delta_: res+=prefix+("approximate_storage_delta: %s\n" % self.DebugFormatInt32(self.approximate_storage_delta_))
     return res
 
 
@@ -3735,6 +3766,7 @@
   kCommitCostGroup = 5
   kCommitCostrequested_entity_puts = 6
   kCommitCostrequested_entity_deletes = 7
+  kapproximate_storage_delta = 8
 
   _TEXT = _BuildTagLookupTable({
     0: "ErrorCode",
@@ -3745,7 +3777,8 @@
     5: "CommitCost",
     6: "requested_entity_puts",
     7: "requested_entity_deletes",
-  }, 7)
+    8: "approximate_storage_delta",
+  }, 8)
 
   _TYPES = _BuildTagLookupTable({
     0: ProtocolBuffer.Encoder.NUMERIC,
@@ -3756,7 +3789,8 @@
     5: ProtocolBuffer.Encoder.STARTGROUP,
     6: ProtocolBuffer.Encoder.NUMERIC,
     7: ProtocolBuffer.Encoder.NUMERIC,
-  }, 7, ProtocolBuffer.Encoder.MAX_TYPE)
+    8: ProtocolBuffer.Encoder.NUMERIC,
+  }, 8, ProtocolBuffer.Encoder.MAX_TYPE)
 
 
   _STYLE = """"""
diff --git a/google/appengine/datastore/datastore_pbs.py b/google/appengine/datastore/datastore_pbs.py
new file mode 100644
index 0000000..4dfb755
--- /dev/null
+++ b/google/appengine/datastore/datastore_pbs.py
@@ -0,0 +1,1603 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+
+
+"""Utilities for converting between v3 and v4 datastore protocol buffers.
+
+This module is internal and should not be used by client applications.
+"""
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+from google.appengine.datastore import entity_pb
+
+from google.appengine.datastore import datastore_pb
+from google.appengine.datastore import datastore_v4_pb
+from google.appengine.datastore import entity_v4_pb
+
+
+
+_MEANING_ATOM_CATEGORY = 1
+_MEANING_URL = 2
+_MEANING_ATOM_TITLE = 3
+_MEANING_ATOM_CONTENT = 4
+_MEANING_ATOM_SUMMARY = 5
+_MEANING_ATOM_AUTHOR = 6
+_MEANING_GD_EMAIL = 8
+_MEANING_GEORSS_POINT = 9
+_MEANING_GD_IM = 10
+_MEANING_GD_PHONENUMBER = 11
+_MEANING_GD_POSTALADDRESS = 12
+_MEANING_PERCENT = 13
+_MEANING_TEXT = 15
+_MEANING_BYTESTRING = 16
+_MEANING_INDEX_ONLY = 18
+_MEANING_PREDEFINED_ENTITY_USER = 20
+_MEANING_PREDEFINED_ENTITY_POINT = 21
+_MEANING_ZLIB = 22
+
+
+_URI_MEANING_ZLIB = 'ZLIB'
+
+
+_MAX_INDEXED_BLOB_BYTES = 500
+
+
+_PROPERTY_NAME_X = 'x'
+_PROPERTY_NAME_Y = 'y'
+
+
+_PROPERTY_NAME_EMAIL = 'email'
+_PROPERTY_NAME_AUTH_DOMAIN = 'auth_domain'
+_PROPERTY_NAME_USER_ID = 'user_id'
+_PROPERTY_NAME_INTERNAL_ID = 'internal_id'
+_PROPERTY_NAME_FEDERATED_IDENTITY = 'federated_identity'
+_PROPERTY_NAME_FEDERATED_PROVIDER = 'federated_provider'
+
+
+_PROPERTY_NAME_KEY = '__key__'
+
+_DEFAULT_GAIA_ID = 0
+
+
+def _is_valid_utf8(s):
+  try:
+    s.decode('utf-8')
+    return True
+  except UnicodeDecodeError:
+    return False
+
+
+def _check_conversion(condition, message):
+  """Asserts a conversion condition and raises an error if it's not met.
+
+  Args:
+    condition: (boolean) condition to enforce
+    message: error message
+
+  Raises:
+    InvalidConversionError: if condition is not met
+  """
+  if not condition:
+    raise InvalidConversionError(message)
+
+
+
+class InvalidConversionError(Exception):
+  """Raised when conversion fails."""
+  pass
+
+
+class _EntityConverter(object):
+  """Converter for entities and keys."""
+
+  def v4_to_v3_reference(self, v4_key, v3_ref):
+    """Converts a v4 Key to a v3 Reference.
+
+    Args:
+      v4_key: an entity_v4_pb.Key
+      v3_ref: an entity_pb.Reference to populate
+    """
+    v3_ref.Clear()
+    if v4_key.has_partition_id():
+      if v4_key.partition_id().has_dataset_id():
+        v3_ref.set_app(v4_key.partition_id().dataset_id())
+      if v4_key.partition_id().has_namespace():
+        v3_ref.set_name_space(v4_key.partition_id().namespace())
+    for v4_element in v4_key.path_element_list():
+      v3_element = v3_ref.mutable_path().add_element()
+      v3_element.set_type(v4_element.kind())
+      if v4_element.has_id():
+        v3_element.set_id(v4_element.id())
+      if v4_element.has_name():
+        v3_element.set_name(v4_element.name())
+
+  def v3_to_v4_key(self, v3_ref, v4_key):
+    """Converts a v3 Reference to a v4 Key.
+
+    Args:
+      v3_ref: an entity_pb.Reference
+      v4_key: an entity_v4_pb.Key to populate
+    """
+    v4_key.Clear()
+    if not v3_ref.app():
+      return
+    v4_key.mutable_partition_id().set_dataset_id(v3_ref.app())
+    if v3_ref.name_space():
+      v4_key.mutable_partition_id().set_namespace(v3_ref.name_space())
+    for v3_element in v3_ref.path().element_list():
+      v4_element = v4_key.add_path_element()
+      v4_element.set_kind(v3_element.type())
+      if v3_element.has_id():
+        v4_element.set_id(v3_element.id())
+      if v3_element.has_name():
+        v4_element.set_name(v3_element.name())
+
+  def v4_to_v3_entity(self, v4_entity, v3_entity):
+    """Converts a v4 Entity to a v3 EntityProto.
+
+    Args:
+      v4_entity: an entity_v4_pb.Entity
+      v3_entity: an entity_pb.EntityProto to populate
+    """
+    v3_entity.Clear()
+    for v4_property in v4_entity.property_list():
+      property_name = v4_property.name()
+      if v4_property.has_value():
+        v4_value = v4_property.value()
+        if v4_value.list_value_list():
+          for v4_sub_value in v4_value.list_value_list():
+            self.__add_v3_property(property_name, True, v4_sub_value, v3_entity)
+        else:
+          self.__add_v3_property(property_name, False, v4_value, v3_entity)
+      else:
+        is_multi = v4_property.deprecated_multi()
+        for v4_value in v4_property.deprecated_value_list():
+          self.__add_v3_property(property_name, is_multi, v4_value, v3_entity)
+    if v4_entity.has_key():
+      v4_key = v4_entity.key()
+      self.v4_to_v3_reference(v4_key, v3_entity.mutable_key())
+      v3_ref = v3_entity.key()
+      if (self.__v3_reference_has_id_or_name(v3_ref)
+          or v3_ref.path().element_size() > 1):
+        self._v3_reference_to_group(v3_ref, v3_entity.mutable_entity_group())
+    else:
+
+
+      pass
+
+  def v3_to_v4_entity(self, v3_entity, v4_entity):
+    """Converts a v3 EntityProto to a v4 Entity.
+
+    Args:
+      v3_entity: an entity_pb.EntityProto
+      v4_entity: an entity_v4_pb.Proto to populate
+    """
+    v4_entity.Clear()
+    self.v3_to_v4_key(v3_entity.key(), v4_entity.mutable_key())
+    if not v3_entity.key().has_app():
+
+      v4_entity.clear_key()
+
+
+
+
+    v4_properties = {}
+    for v3_property in v3_entity.property_list():
+      self.__add_v4_property_to_entity(v4_entity, v4_properties, v3_property,
+                                       True)
+    for v3_property in v3_entity.raw_property_list():
+      self.__add_v4_property_to_entity(v4_entity, v4_properties, v3_property,
+                                       False)
+
+  def v4_value_to_v3_property_value(self, v4_value, v3_value):
+    """Converts a v4 Value to a v3 PropertyValue.
+
+    Args:
+      v4_value: an entity_v4_pb.Value
+      v3_value: an entity_pb.PropertyValue to populate
+    """
+    v3_value.Clear()
+    if v4_value.has_boolean_value():
+      v3_value.set_booleanvalue(v4_value.boolean_value())
+    elif v4_value.has_integer_value():
+      v3_value.set_int64value(v4_value.integer_value())
+    elif v4_value.has_double_value():
+      v3_value.set_doublevalue(v4_value.double_value())
+    elif v4_value.has_timestamp_microseconds_value():
+      v3_value.set_int64value(v4_value.timestamp_microseconds_value())
+    elif v4_value.has_key_value():
+      v3_ref = entity_pb.Reference()
+      self.v4_to_v3_reference(v4_value.key_value(), v3_ref)
+      self._v3_reference_to_v3_property_value(v3_ref, v3_value)
+    elif v4_value.has_blob_key_value():
+      v3_value.set_stringvalue(v4_value.blob_key_value())
+    elif v4_value.has_string_value():
+      v3_value.set_stringvalue(v4_value.string_value())
+    elif v4_value.has_blob_value():
+      v3_value.set_stringvalue(v4_value.blob_value())
+    elif v4_value.has_entity_value():
+      v4_entity_value = v4_value.entity_value()
+      v4_meaning = v4_value.meaning()
+      if (v4_meaning == _MEANING_GEORSS_POINT
+          or v4_meaning == _MEANING_PREDEFINED_ENTITY_POINT):
+        self.__v4_to_v3_point_value(v4_entity_value,
+                                    v3_value.mutable_pointvalue())
+      elif v4_meaning == _MEANING_PREDEFINED_ENTITY_USER:
+        self.__v4_to_v3_user_value(v4_entity_value,
+                                   v3_value.mutable_uservalue())
+      else:
+        v3_entity_value = entity_pb.EntityProto()
+        self.v4_to_v3_entity(v4_entity_value, v3_entity_value)
+        v3_value.set_stringvalue(v3_entity_value.SerializePartialToString())
+    else:
+
+      pass
+
+  def v3_property_to_v4_value(self, v3_property, indexed, v4_value):
+    """Converts a v3 Property to a v4 Value.
+
+    Args:
+      v3_property: an entity_pb.Property
+      indexed: whether the v3 property is indexed
+      v4_value: an entity_v4_pb.Value to populate
+    """
+    v4_value.Clear()
+    v3_property_value = v3_property.value()
+    v3_meaning = v3_property.meaning()
+    v3_uri_meaning = None
+    if v3_property.meaning_uri():
+      v3_uri_meaning = v3_property.meaning_uri()
+
+    if not self.__is_v3_property_value_union_valid(v3_property_value):
+
+
+      v3_meaning = None
+      v3_uri_meaning = None
+    elif v3_meaning == entity_pb.Property.NO_MEANING:
+      v3_meaning = None
+    elif not self.__is_v3_property_value_meaning_valid(v3_property_value,
+                                                       v3_meaning):
+
+      v3_meaning = None
+
+    is_zlib_value = False
+    if v3_uri_meaning:
+      if v3_uri_meaning == _URI_MEANING_ZLIB:
+        if v3_property_value.has_stringvalue():
+          is_zlib_value = True
+          if v3_meaning != entity_pb.Property.BLOB:
+
+            v3_meaning = entity_pb.Property.BLOB
+        else:
+          pass
+      else:
+        pass
+
+
+    if v3_property_value.has_booleanvalue():
+      v4_value.set_boolean_value(v3_property_value.booleanvalue())
+    elif v3_property_value.has_int64value():
+      if v3_meaning == entity_pb.Property.GD_WHEN:
+        v4_value.set_timestamp_microseconds_value(
+            v3_property_value.int64value())
+        v3_meaning = None
+      else:
+        v4_value.set_integer_value(v3_property_value.int64value())
+    elif v3_property_value.has_doublevalue():
+      v4_value.set_double_value(v3_property_value.doublevalue())
+    elif v3_property_value.has_referencevalue():
+      v3_ref = entity_pb.Reference()
+      self.__v3_reference_value_to_v3_reference(
+          v3_property_value.referencevalue(), v3_ref)
+      self.v3_to_v4_key(v3_ref, v4_value.mutable_key_value())
+    elif v3_property_value.has_stringvalue():
+      if v3_meaning == entity_pb.Property.ENTITY_PROTO:
+        serialized_entity_v3 = v3_property_value.stringvalue()
+        v3_entity = entity_pb.EntityProto()
+
+
+        v3_entity.ParsePartialFromString(serialized_entity_v3)
+        self.v3_to_v4_entity(v3_entity, v4_value.mutable_entity_value())
+        v3_meaning = None
+      elif (v3_meaning == entity_pb.Property.BLOB
+            or v3_meaning == entity_pb.Property.BYTESTRING):
+        v4_value.set_blob_value(v3_property_value.stringvalue())
+
+        if indexed or v3_meaning == entity_pb.Property.BLOB:
+          v3_meaning = None
+      else:
+        string_value = v3_property_value.stringvalue()
+        if _is_valid_utf8(string_value):
+          if v3_meaning == entity_pb.Property.BLOBKEY:
+            v4_value.set_blob_key_value(string_value)
+            v3_meaning = None
+          else:
+            v4_value.set_string_value(string_value)
+        else:
+
+          v4_value.set_blob_value(string_value)
+
+          if v3_meaning != entity_pb.Property.INDEX_VALUE:
+            v3_meaning = None
+
+
+    elif v3_property_value.has_pointvalue():
+      self.__v3_to_v4_point_entity(v3_property_value.pointvalue(),
+                                   v4_value.mutable_entity_value())
+      if v3_meaning != entity_pb.Property.GEORSS_POINT:
+        v4_value.set_meaning(_MEANING_PREDEFINED_ENTITY_POINT)
+        v3_meaning = None
+    elif v3_property_value.has_uservalue():
+      self.__v3_to_v4_user_entity(v3_property_value.uservalue(),
+                                  v4_value.mutable_entity_value())
+      v4_value.set_meaning(_MEANING_PREDEFINED_ENTITY_USER)
+    else:
+      pass
+
+    if is_zlib_value:
+      v4_value.set_meaning(_MEANING_ZLIB)
+    elif v3_meaning:
+      v4_value.set_meaning(v3_meaning)
+
+
+    if indexed != v4_value.indexed():
+      v4_value.set_indexed(indexed)
+
+  def __v4_to_v3_property(self, property_name, is_multi, v4_value, v3_property):
+    """Converts info from a v4 Property to a v3 Property.
+
+    v4_value must not have a list_value.
+
+    Args:
+      property_name: the name of the property
+      is_multi: whether the property contains multiple values
+      v4_value: an entity_v4_pb.Value
+      v3_property: an entity_pb.Property to populate
+    """
+    assert not v4_value.list_value_list(), 'v4 list_value not convertable to v3'
+    v3_property.Clear()
+    v3_property.set_name(property_name)
+    v3_property.set_multiple(is_multi)
+    self.v4_value_to_v3_property_value(v4_value, v3_property.mutable_value())
+
+    v4_meaning = None
+    if v4_value.has_meaning():
+      v4_meaning = v4_value.meaning()
+
+    if v4_value.has_timestamp_microseconds_value():
+      v3_property.set_meaning(entity_pb.Property.GD_WHEN)
+    elif v4_value.has_blob_key_value():
+      v3_property.set_meaning(entity_pb.Property.BLOBKEY)
+    elif v4_value.has_blob_value():
+      if v4_meaning == _MEANING_ZLIB:
+        v3_property.set_meaning_uri(_URI_MEANING_ZLIB)
+      if v4_meaning == entity_pb.Property.BYTESTRING:
+        if v4_value.indexed():
+          pass
+
+
+      else:
+        if v4_value.indexed():
+          v3_property.set_meaning(entity_pb.Property.BYTESTRING)
+        else:
+          v3_property.set_meaning(entity_pb.Property.BLOB)
+        v4_meaning = None
+    elif v4_value.has_entity_value():
+      if v4_meaning != _MEANING_GEORSS_POINT:
+        if (v4_meaning != _MEANING_PREDEFINED_ENTITY_POINT
+            and v4_meaning != _MEANING_PREDEFINED_ENTITY_USER):
+          v3_property.set_meaning(entity_pb.Property.ENTITY_PROTO)
+        v4_meaning = None
+    else:
+
+      pass
+    if v4_meaning is not None:
+      v3_property.set_meaning(v4_meaning)
+
+  def __add_v3_property(self, property_name, is_multi, v4_value, v3_entity):
+    """Adds a v3 Property to an Entity based on information from a v4 Property.
+
+    Args:
+      property_name: the name of the property
+      is_multi: whether the property contains multiple values
+      v4_value: an entity_v4_pb.Value
+      v3_entity: an entity_pb.EntityProto
+    """
+    if v4_value.indexed():
+      self.__v4_to_v3_property(property_name, is_multi, v4_value,
+                               v3_entity.add_property())
+    else:
+      self.__v4_to_v3_property(property_name, is_multi, v4_value,
+                               v3_entity.add_raw_property())
+
+  def __build_name_to_v4_property_map(self, v4_entity):
+    property_map = {}
+    for prop in v4_entity.property_list():
+      property_map[prop.name()] = prop
+    return property_map
+
+  def __add_v4_property_to_entity(self, v4_entity, property_map, v3_property,
+                                  indexed):
+    """Adds a v4 Property to an entity or modifies an existing one.
+
+    property_map is used to track of properties that have already been added.
+    The same dict should be used for all of an entity's properties.
+
+    Args:
+      v4_entity: an entity_v4_pb.Entity
+      property_map: a dict of name -> v4_property
+      v3_property: an entity_pb.Property to convert to v4 and add to the dict
+      indexed: whether the property is indexed
+    """
+    property_name = v3_property.name()
+    if property_name in property_map:
+      v4_property = property_map[property_name]
+    else:
+      v4_property = v4_entity.add_property()
+      v4_property.set_name(property_name)
+      property_map[property_name] = v4_property
+    if v3_property.multiple():
+      self.v3_property_to_v4_value(v3_property, indexed,
+                                   v4_property.mutable_value().add_list_value())
+    else:
+      self.v3_property_to_v4_value(v3_property, indexed,
+                                   v4_property.mutable_value())
+
+  def __get_single_v4_integer_value(self, v4_property):
+    """Returns an integer value from a v4 Property.
+
+    Args:
+      v4_property: an entity_v4_pb.Property
+
+    Returns:
+      an integer
+
+    Throws:
+      AssertionError if v4_property doesn't contain exactly one value
+    """
+    if v4_property.has_value():
+      return v4_property.value().integer_value()
+    else:
+      v4_values = v4_property.deprecated_value_list()
+      assert len(v4_values) == 1, 'property had %d values' % len(v4_values)
+      return v4_values[0].integer_value()
+
+  def __get_single_v4_double_value(self, v4_property):
+    """Returns a double value from a v4 Property.
+
+    Args:
+      v4_property: an entity_v4_pb.Property
+
+    Returns:
+      a double
+
+    Throws:
+      AssertionError if v4_property doesn't contain exactly one value
+    """
+    if v4_property.has_value():
+      return v4_property.value().double_value()
+    else:
+      v4_values = v4_property.deprecated_value_list()
+      assert len(v4_values) == 1, 'property had %d values' % len(v4_values)
+      return v4_values[0].double_value()
+
+  def __get_single_v4_string_value(self, v4_property):
+    """Returns an string value from a v4 Property.
+
+    Args:
+      v4_property: an entity_v4_pb.Property
+
+    Returns:
+      a string
+
+    Throws:
+      AssertionError if v4_property doesn't contain exactly one value
+    """
+    if v4_property.has_value():
+      return v4_property.value().string_value()
+    else:
+      v4_values = v4_property.deprecated_value_list()
+      assert len(v4_values) == 1, 'property had %d values' % len(v4_values)
+      return v4_values[0].string_value()
+
+  def __v4_integer_property(self, name, value, indexed):
+    """Creates a single-integer-valued v4 Property.
+
+    Args:
+      name: the property name
+      value: the integer value of the property
+      indexed: whether the value should be indexed
+
+    Returns:
+      an entity_v4_pb.Property
+    """
+    v4_property = entity_v4_pb.Property()
+    v4_property.set_name(name)
+    v4_value = v4_property.mutable_value()
+    v4_value.set_indexed(indexed)
+    v4_value.set_integer_value(value)
+    return v4_property
+
+  def __v4_double_property(self, name, value, indexed):
+    """Creates a single-double-valued v4 Property.
+
+    Args:
+      name: the property name
+      value: the double value of the property
+      indexed: whether the value should be indexed
+
+    Returns:
+      an entity_v4_pb.Property
+    """
+    v4_property = entity_v4_pb.Property()
+    v4_property.set_name(name)
+    v4_value = v4_property.mutable_value()
+    v4_value.set_indexed(indexed)
+    v4_value.set_double_value(value)
+    return v4_property
+
+  def __v4_string_property(self, name, value, indexed):
+    """Creates a single-string-valued v4 Property.
+
+    Args:
+      name: the property name
+      value: the string value of the property
+      indexed: whether the value should be indexed
+
+    Returns:
+      an entity_v4_pb.Property
+    """
+    v4_property = entity_v4_pb.Property()
+    v4_property.set_name(name)
+    v4_value = v4_property.mutable_value()
+    v4_value.set_indexed(indexed)
+    v4_value.set_string_value(value)
+    return v4_property
+
+  def __v4_to_v3_point_value(self, v4_point_entity, v3_point_value):
+    """Converts a v4 point Entity to a v3 PointValue.
+
+    Args:
+      v4_point_entity: an entity_v4_pb.Entity representing a point
+      v3_point_value: an entity_pb.Property_PointValue to populate
+    """
+    v3_point_value.Clear()
+    name_to_v4_property = self.__build_name_to_v4_property_map(v4_point_entity)
+    v3_point_value.set_x(
+        self.__get_single_v4_double_value(name_to_v4_property['x']))
+    v3_point_value.set_y(
+        self.__get_single_v4_double_value(name_to_v4_property['y']))
+
+  def __v3_to_v4_point_entity(self, v3_point_value, v4_entity):
+    """Converts a v3 UserValue to a v4 user Entity.
+
+    Args:
+      v3_point_value: an entity_pb.Property_PointValue
+      v4_entity: an entity_v4_pb.Entity to populate
+    """
+    v4_entity.Clear()
+    v4_entity.property_list().append(
+        self.__v4_double_property(_PROPERTY_NAME_X, v3_point_value.x(), False))
+    v4_entity.property_list().append(
+        self.__v4_double_property(_PROPERTY_NAME_Y, v3_point_value.y(), False))
+
+  def __v4_to_v3_user_value(self, v4_user_entity, v3_user_value):
+    """Converts a v4 user Entity to a v3 UserValue.
+
+    Args:
+      v4_user_entity: an entity_v4_pb.Entity representing a user
+      v3_user_value: an entity_pb.Property_UserValue to populate
+    """
+    v3_user_value.Clear()
+    name_to_v4_property = self.__build_name_to_v4_property_map(v4_user_entity)
+
+    v3_user_value.set_email(self.__get_single_v4_string_value(
+        name_to_v4_property[_PROPERTY_NAME_EMAIL]))
+    v3_user_value.set_auth_domain(self.__get_single_v4_string_value(
+        name_to_v4_property[_PROPERTY_NAME_AUTH_DOMAIN]))
+    if _PROPERTY_NAME_USER_ID in name_to_v4_property:
+      v3_user_value.set_obfuscated_gaiaid(
+          self.__get_single_v4_string_value(
+              name_to_v4_property[_PROPERTY_NAME_USER_ID]))
+    if _PROPERTY_NAME_INTERNAL_ID in name_to_v4_property:
+      v3_user_value.set_gaiaid(self.__get_single_v4_integer_value(
+          name_to_v4_property[_PROPERTY_NAME_INTERNAL_ID]))
+    else:
+
+      v3_user_value.set_gaiaid(0)
+    if _PROPERTY_NAME_FEDERATED_IDENTITY in name_to_v4_property:
+      v3_user_value.set_federated_identity(
+          self.__get_single_v4_string_value(name_to_v4_property[
+              _PROPERTY_NAME_FEDERATED_IDENTITY]))
+    if _PROPERTY_NAME_FEDERATED_PROVIDER in name_to_v4_property:
+      v3_user_value.set_federated_provider(
+          self.__get_single_v4_string_value(name_to_v4_property[
+              _PROPERTY_NAME_FEDERATED_PROVIDER]))
+
+  def __v3_to_v4_user_entity(self, v3_user_value, v4_entity):
+    """Converts a v3 UserValue to a v4 user Entity.
+
+    Args:
+      v3_user_value: an entity_pb.Property_UserValue
+      v4_entity: an entity_v4_pb.Entity to populate
+    """
+    v4_entity.Clear()
+    v4_entity.property_list().append(
+        self.__v4_string_property(_PROPERTY_NAME_EMAIL, v3_user_value.email(),
+                                  False))
+    v4_entity.property_list().append(self.__v4_string_property(
+        _PROPERTY_NAME_AUTH_DOMAIN,
+        v3_user_value.auth_domain(), False))
+
+    if v3_user_value.gaiaid() != 0:
+      v4_entity.property_list().append(self.__v4_integer_property(
+          _PROPERTY_NAME_INTERNAL_ID,
+          v3_user_value.gaiaid(),
+          False))
+    if v3_user_value.has_obfuscated_gaiaid():
+      v4_entity.property_list().append(self.__v4_string_property(
+          _PROPERTY_NAME_USER_ID,
+          v3_user_value.obfuscated_gaiaid(),
+          False))
+    if v3_user_value.has_federated_identity():
+      v4_entity.property_list().append(self.__v4_string_property(
+          _PROPERTY_NAME_FEDERATED_IDENTITY,
+          v3_user_value.federated_identity(),
+          False))
+    if v3_user_value.has_federated_provider():
+      v4_entity.property_list().append(self.__v4_string_property(
+          _PROPERTY_NAME_FEDERATED_PROVIDER,
+          v3_user_value.federated_provider(),
+          False))
+
+  def __is_v3_property_value_union_valid(self, v3_property_value):
+    """Returns True if the v3 PropertyValue's union is valid."""
+    num_sub_values = 0
+    if v3_property_value.has_booleanvalue():
+      num_sub_values += 1
+    if v3_property_value.has_int64value():
+      num_sub_values += 1
+    if v3_property_value.has_doublevalue():
+      num_sub_values += 1
+    if v3_property_value.has_referencevalue():
+      num_sub_values += 1
+    if v3_property_value.has_stringvalue():
+      num_sub_values += 1
+    if v3_property_value.has_pointvalue():
+      num_sub_values += 1
+    if v3_property_value.has_uservalue():
+      num_sub_values += 1
+    return num_sub_values <= 1
+
+  def __is_v3_property_value_meaning_valid(self, v3_property_value, v3_meaning):
+    """Returns True if the v3 PropertyValue's type value matches its meaning."""
+    def ReturnTrue():
+      return True
+    def HasStringValue():
+      return v3_property_value.has_stringvalue()
+    def HasInt64Value():
+      return v3_property_value.has_int64value()
+    def HasPointValue():
+      return v3_property_value.has_pointvalue()
+    def ReturnFalse():
+      return False
+    value_checkers = {
+        entity_pb.Property.NO_MEANING: ReturnTrue,
+        entity_pb.Property.INDEX_VALUE: ReturnTrue,
+        entity_pb.Property.BLOB: HasStringValue,
+        entity_pb.Property.TEXT: HasStringValue,
+        entity_pb.Property.BYTESTRING: HasStringValue,
+        entity_pb.Property.ATOM_CATEGORY: HasStringValue,
+        entity_pb.Property.ATOM_LINK: HasStringValue,
+        entity_pb.Property.ATOM_TITLE: HasStringValue,
+        entity_pb.Property.ATOM_CONTENT: HasStringValue,
+        entity_pb.Property.ATOM_SUMMARY: HasStringValue,
+        entity_pb.Property.ATOM_AUTHOR: HasStringValue,
+        entity_pb.Property.GD_EMAIL: HasStringValue,
+        entity_pb.Property.GD_IM: HasStringValue,
+        entity_pb.Property.GD_PHONENUMBER: HasStringValue,
+        entity_pb.Property.GD_POSTALADDRESS: HasStringValue,
+        entity_pb.Property.BLOBKEY: HasStringValue,
+        entity_pb.Property.ENTITY_PROTO: HasStringValue,
+        entity_pb.Property.GD_WHEN: HasInt64Value,
+        entity_pb.Property.GD_RATING: HasInt64Value,
+        entity_pb.Property.GEORSS_POINT: HasPointValue,
+        }
+    default = ReturnFalse
+    return value_checkers.get(v3_meaning, default)()
+
+  def __v3_reference_has_id_or_name(self, v3_ref):
+    """Determines if a v3 Reference specifies an ID or name.
+
+    Args:
+      v3_ref: an entity_pb.Reference
+
+    Returns:
+      boolean: True if the last path element specifies an ID or name.
+    """
+    path = v3_ref.path()
+    assert path.element_size() >= 1
+    last_element = path.element(path.element_size() - 1)
+    return last_element.has_id() or last_element.has_name()
+
+  def _v3_reference_to_group(self, v3_ref, group):
+    """Converts a v3 Reference to a v3 Path representing the entity group.
+
+    The entity group is represented as an entity_pb.Path containing only the
+    first element in the provided Reference.
+
+    Args:
+      v3_ref: an entity_pb.Reference
+      group: an entity_pb.Path to populate
+    """
+    group.Clear()
+    path = v3_ref.path()
+    assert path.element_size() >= 1
+    group.add_element().CopyFrom(path.element(0))
+
+  def _v3_reference_to_v3_property_value(self, v3_ref, v3_property_value):
+    """Converts a v3 Reference to a v3 PropertyValue.
+
+    Args:
+      v3_ref: an entity_pb.Reference
+      v3_property_value: an entity_pb.PropertyValue to populate
+    """
+    v3_property_value.Clear()
+    reference_value = v3_property_value.mutable_referencevalue()
+    if v3_ref.has_app():
+      reference_value.set_app(v3_ref.app())
+    if v3_ref.has_name_space():
+      reference_value.set_name_space(v3_ref.name_space())
+    for v3_path_element in v3_ref.path().element_list():
+      v3_ref_value_path_element = reference_value.add_pathelement()
+      if v3_path_element.has_type():
+        v3_ref_value_path_element.set_type(v3_path_element.type())
+      if v3_path_element.has_id():
+        v3_ref_value_path_element.set_id(v3_path_element.id())
+      if v3_path_element.has_name():
+        v3_ref_value_path_element.set_name(v3_path_element.name())
+
+  def __v3_reference_value_to_v3_reference(self, v3_ref_value, v3_ref):
+    """Converts a v3 ReferenceValue to a v3 Reference.
+
+    Args:
+      v3_ref_value: an entity_pb.PropertyValue_ReferenceValue
+      v3_ref: an entity_pb.Reference to populate
+    """
+    v3_ref.Clear()
+    if v3_ref_value.has_app():
+      v3_ref.set_app(v3_ref_value.app())
+    if v3_ref_value.has_name_space():
+      v3_ref.set_name_space(v3_ref_value.name_space())
+    for v3_ref_value_path_element in v3_ref_value.pathelement_list():
+      v3_path_element = v3_ref.mutable_path().add_element()
+      if v3_ref_value_path_element.has_type():
+        v3_path_element.set_type(v3_ref_value_path_element.type())
+      if v3_ref_value_path_element.has_id():
+        v3_path_element.set_id(v3_ref_value_path_element.id())
+      if v3_ref_value_path_element.has_name():
+        v3_path_element.set_name(v3_ref_value_path_element.name())
+
+
+
+__entity_converter = _EntityConverter()
+
+
+def get_entity_converter():
+  """Returns a converter for v3 and v4 entities and keys."""
+  return __entity_converter
+
+
+class _BaseQueryConverter(object):
+  """Base converter for queries."""
+
+  def __init__(self, entity_converter):
+    self._entity_converter = entity_converter
+
+  def v4_to_v3_compiled_cursor(self, v4_cursor, v3_compiled_cursor):
+    """Converts a v4 cursor string to a v3 CompiledCursor.
+
+    Args:
+      v4_cursor: a string representing a v4 query cursor
+      v3_compiled_cursor: a datastore_pb.CompiledCursor to populate
+    """
+    raise NotImplementedError
+
+  def v3_to_v4_compiled_cursor(self, v3_compiled_cursor):
+    """Converts a v3 CompiledCursor to a v4 cursor string.
+
+    Args:
+      v3_compiled_cursor: a datastore_pb.CompiledCursor
+
+    Returns:
+      a string representing a v4 query cursor
+    """
+    raise NotImplementedError
+
+  def v4_to_v3_query(self, v4_partition_id, v4_query, v3_query):
+    """Converts a v4 Query to a v3 Query.
+
+    Args:
+      v4_partition_id: a datastore_v4_pb.PartitionId
+      v4_query: a datastore_v4_pb.Query
+      v3_query: a datastore_pb.Query to populate
+
+    Raises:
+      InvalidConversionError if the query cannot be converted
+    """
+    v3_query.Clear()
+
+    if v4_partition_id.dataset_id():
+      v3_query.set_app(v4_partition_id.dataset_id())
+    if v4_partition_id.has_namespace():
+      v3_query.set_name_space(v4_partition_id.namespace())
+
+    v3_query.set_persist_offset(True)
+    v3_query.set_require_perfect_plan(True)
+    v3_query.set_compile(True)
+
+
+    if v4_query.has_limit():
+      v3_query.set_limit(v4_query.limit())
+    if v4_query.offset():
+      v3_query.set_offset(v4_query.offset())
+    if v4_query.has_start_cursor():
+      self.v4_to_v3_compiled_cursor(v4_query.start_cursor(),
+                                    v3_query.mutable_compiled_cursor())
+    if v4_query.has_end_cursor():
+      self.v4_to_v3_compiled_cursor(v4_query.end_cursor(),
+                                    v3_query.mutable_end_compiled_cursor())
+
+
+    if v4_query.kind_list():
+      _check_conversion(len(v4_query.kind_list()) == 1,
+                        'multiple kinds not supported')
+      v3_query.set_kind(v4_query.kind(0).name())
+
+
+    has_key_projection = False
+    for prop in v4_query.projection_list():
+      if prop.property().name() == _PROPERTY_NAME_KEY:
+        has_key_projection = True
+      else:
+        v3_query.add_property_name(prop.property().name())
+    if has_key_projection and not v3_query.property_name_list():
+      v3_query.set_keys_only(True)
+
+
+    for prop in v4_query.group_by_list():
+      v3_query.add_group_by_property_name(prop.name())
+
+
+    self.__populate_v3_filters(v4_query.filter(), v3_query)
+
+
+    for v4_order in v4_query.order_list():
+      v3_order = v3_query.add_order()
+      v3_order.set_property(v4_order.property().name())
+      if v4_order.has_direction():
+        v3_order.set_direction(v4_order.direction())
+
+  def v3_to_v4_query(self, v3_query, v4_query):
+    """Converts a v3 Query to a v4 Query.
+
+    Args:
+      v3_query: a datastore_pb.Query
+      v4_query: a datastore_v4_pb.Query to populate
+
+    Raises:
+      InvalidConversionError if the query cannot be converted
+    """
+    v4_query.Clear()
+
+    _check_conversion(not v3_query.has_distinct(),
+                      'distinct option not supported')
+    _check_conversion(v3_query.require_perfect_plan(),
+                      'non-perfect plans not supported')
+
+
+
+    if v3_query.has_limit():
+      v4_query.set_limit(v3_query.limit())
+    if v3_query.offset():
+      v4_query.set_offset(v3_query.offset())
+    if v3_query.has_compiled_cursor():
+      v4_query.set_start_cursor(
+          self.v3_to_v4_compiled_cursor(v3_query.compiled_cursor()))
+    if v3_query.has_end_compiled_cursor():
+      v4_query.set_end_cursor(
+          self.v3_to_v4_compiled_cursor(v3_query.end_compiled_cursor()))
+
+
+    if v3_query.has_kind():
+      v4_query.add_kind().set_name(v3_query.kind())
+
+
+    for name in v3_query.property_name_list():
+      v4_query.add_projection().mutable_property().set_name(name)
+    if v3_query.keys_only():
+      v4_query.add_projection().mutable_property().set_name(_PROPERTY_NAME_KEY)
+
+
+    for name in v3_query.group_by_property_name_list():
+      v4_query.add_group_by().set_name(name)
+
+
+    num_v4_filters = len(v3_query.filter_list())
+    if v3_query.has_ancestor():
+      num_v4_filters += 1
+
+    if num_v4_filters == 1:
+      get_property_filter = self.__get_property_filter
+    elif num_v4_filters >= 1:
+      v4_query.mutable_filter().mutable_composite_filter().set_operator(
+          datastore_v4_pb.CompositeFilter.AND)
+      get_property_filter = self.__add_property_filter
+
+    if v3_query.has_ancestor():
+      self.__v3_query_to_v4_ancestor_filter(v3_query,
+                                            get_property_filter(v4_query))
+    for v3_filter in v3_query.filter_list():
+      self.__v3_filter_to_v4_property_filter(v3_filter,
+                                             get_property_filter(v4_query))
+
+
+    for v3_order in v3_query.order_list():
+      v4_order = v4_query.add_order()
+      v4_order.mutable_property().set_name(v3_order.property())
+      if v3_order.has_direction():
+        v4_order.set_direction(v3_order.direction())
+
+  def __get_property_filter(self, v4_query):
+    """Returns the PropertyFilter from the query's top-level filter."""
+    return v4_query.mutable_filter().mutable_property_filter()
+
+  def __add_property_filter(self, v4_query):
+    """Adds and returns a PropertyFilter from the query's composite filter."""
+    v4_comp_filter = v4_query.mutable_filter().mutable_composite_filter()
+    return v4_comp_filter.add_filter().mutable_property_filter()
+
+  def __populate_v3_filters(self, v4_filter, v3_query):
+    """Populates a filters for a v3 Query.
+
+    Args:
+      v4_filter: a datastore_v4_pb.Filter
+      v3_query: a datastore_pb.Query to populate with filters
+    """
+    if v4_filter.has_property_filter():
+      v4_property_filter = v4_filter.property_filter()
+      if (v4_property_filter.operator()
+          == datastore_v4_pb.PropertyFilter.HAS_ANCESTOR):
+        _check_conversion(v4_property_filter.value().has_key_value(),
+                          'HAS_ANCESTOR requires a reference value')
+        _check_conversion((v4_property_filter.property().name()
+                           == _PROPERTY_NAME_KEY),
+                          'unsupported property')
+        _check_conversion(not v3_query.has_ancestor(),
+                          'duplicate ancestor constraint')
+        self._entity_converter.v4_to_v3_reference(
+            v4_property_filter.value().key_value(),
+            v3_query.mutable_ancestor())
+      else:
+        v3_filter = v3_query.add_filter()
+        property_name = v4_property_filter.property().name()
+        v3_filter.set_op(v4_property_filter.operator())
+        _check_conversion(not v4_property_filter.value().list_value_list(),
+                          ('unsupported value type, %s, in property filter'
+                           ' on "%s"' % ('list_value', property_name)))
+        prop = v3_filter.add_property()
+        prop.set_multiple(False)
+        prop.set_name(property_name)
+        self._entity_converter.v4_value_to_v3_property_value(
+            v4_property_filter.value(), prop.mutable_value())
+    elif v4_filter.has_composite_filter():
+      _check_conversion((v4_filter.composite_filter().operator()
+                         == datastore_v4_pb.CompositeFilter.AND),
+                        'unsupported composite property operator')
+      for v4_sub_filter in v4_filter.composite_filter().filter_list():
+        self.__populate_v3_filters(v4_sub_filter, v3_query)
+
+  def __v3_filter_to_v4_property_filter(self, v3_filter, v4_property_filter):
+    """Converts a v3 Filter to a v4 PropertyFilter.
+
+    Args:
+      v3_filter: a datastore_pb.Filter
+      v4_property_filter: a datastore_v4_pb.PropertyFilter to populate
+
+    Raises:
+      InvalidConversionError if the filter cannot be converted
+    """
+    _check_conversion(v3_filter.property_size() == 1, 'invalid filter')
+    _check_conversion(v3_filter.op() <= 5,
+                      'unsupported filter op: %d' % v3_filter.op())
+    v4_property_filter.Clear()
+    v4_property_filter.set_operator(v3_filter.op())
+    v4_property_filter.mutable_property().set_name(v3_filter.property(0).name())
+    self._entity_converter.v3_property_to_v4_value(
+        v3_filter.property(0), True, v4_property_filter.mutable_value())
+
+  def __v3_query_to_v4_ancestor_filter(self, v3_query, v4_property_filter):
+    """Converts a v3 Query to a v4 ancestor PropertyFilter.
+
+    Args:
+      v3_query: a datastore_pb.Query
+      v4_property_filter: a datastore_v4_pb.PropertyFilter to populate
+    """
+    v4_property_filter.Clear()
+    v4_property_filter.set_operator(
+        datastore_v4_pb.PropertyFilter.HAS_ANCESTOR)
+    prop = v4_property_filter.mutable_property()
+    prop.set_name(_PROPERTY_NAME_KEY)
+    self._entity_converter.v3_to_v4_key(
+        v3_query.ancestor(),
+        v4_property_filter.mutable_value().mutable_key_value())
+
+
+class _StubQueryConverter(_BaseQueryConverter):
+  """A query converter suitable for use in stubs."""
+
+  def v4_to_v3_compiled_cursor(self, v4_cursor, v3_compiled_cursor):
+    v3_compiled_cursor.Clear()
+    v3_compiled_cursor.ParseFromString(v4_cursor)
+
+  def v3_to_v4_compiled_cursor(self, v3_compiled_cursor):
+    return v3_compiled_cursor.SerializeToString()
+
+
+
+__stub_query_converter = _StubQueryConverter(__entity_converter)
+
+
+def get_stub_query_converter():
+  """Returns a converter for v3 and v4 queries (not suitable for production).
+
+  This converter is suitable for use in stubs but not for production.
+
+  Returns:
+    a _StubQueryConverter
+  """
+  return __stub_query_converter
+
+
+class _BaseServiceConverter(object):
+  """Base converter for v3 and v4 request/response protos."""
+
+  def __init__(self, entity_converter, query_converter):
+    self._entity_converter = entity_converter
+    self._query_converter = query_converter
+
+  def v4_to_v3_cursor(self, v4_query_handle, v3_cursor):
+    """Converts a v4 cursor string to a v3 Cursor.
+
+    Args:
+      v4_query_handle: a string representing a v4 query handle
+      v3_cursor: a datastore_pb.Cursor to populate
+    """
+    raise NotImplementedError
+
+  def _v3_to_v4_query_handle(self, v3_cursor):
+    """Converts a v3 Cursor to a v4 query handle string.
+
+    Args:
+      v3_cursor: a datastore_pb.Cursor
+
+    Returns:
+      a string representing a v4 cursor
+    """
+    raise NotImplementedError
+
+  def v4_to_v3_txn(self, v4_txn, v3_txn):
+    """Converts a v4 transaction string to a v3 Transaction.
+
+    Args:
+      v4_txn: a string representing a v4 transaction
+      v3_txn: a datastore_pb.Transaction to populate
+    """
+    raise NotImplementedError
+
+  def _v3_to_v4_txn(self, v3_txn):
+    """Converts a v3 Transaction to a v4 transaction string.
+
+    Args:
+      v3_txn: a datastore_pb.Transaction
+
+    Returns:
+      a string representing a v4 transaction
+    """
+    raise NotImplementedError
+
+
+
+
+  def v4_to_v3_begin_transaction_req(self, app_id, v4_req):
+    """Converts a v4 BeginTransactionRequest to a v3 BeginTransactionRequest.
+
+    Args:
+      app_id: app id
+      v4_req: a datastore_v4_pb.BeginTransactionRequest
+
+    Returns:
+      a datastore_pb.BeginTransactionRequest
+    """
+    v3_req = datastore_pb.BeginTransactionRequest()
+    v3_req.set_app(app_id)
+    v3_req.set_allow_multiple_eg(v4_req.cross_group())
+    return v3_req
+
+  def v3_to_v4_begin_transaction_req(self, v3_req):
+    """Converts a v3 BeginTransactionRequest to a v4 BeginTransactionRequest.
+
+    Args:
+      v3_req: a datastore_pb.BeginTransactionRequest
+
+    Returns:
+      a datastore_v4_pb.BeginTransactionRequest
+    """
+    v4_req = datastore_v4_pb.BeginTransactionRequest()
+
+    if v3_req.has_allow_multiple_eg():
+      v4_req.set_cross_group(v3_req.allow_multiple_eg())
+
+    return v4_req
+
+  def v4_begin_transaction_resp_to_v3_txn(self, v4_resp):
+    """Converts a v4 BeginTransactionResponse to a v3 Transaction.
+
+    Args:
+      v4_resp: datastore_v4_pb.BeginTransactionResponse
+
+    Returns:
+      a a datastore_pb.Transaction
+    """
+    v3_txn = datastore_pb.Transaction()
+    self.v4_to_v3_txn(v4_resp.transaction(), v3_txn)
+    return v3_txn
+
+  def v3_to_v4_begin_transaction_resp(self, v3_resp):
+    """Converts a v3 Transaction to a v4 BeginTransactionResponse.
+
+    Args:
+      v3_resp: a datastore_pb.Transaction
+
+    Returns:
+      a datastore_v4_pb.BeginTransactionResponse
+    """
+    v4_resp = datastore_v4_pb.BeginTransactionResponse()
+    v4_resp.set_transaction(self._v3_to_v4_txn(v3_resp))
+    return v4_resp
+
+
+
+
+  def v4_rollback_req_to_v3_txn(self, v4_req):
+    """Converts a v4 RollbackRequest to a v3 Transaction.
+
+    Args:
+      v4_req: a datastore_v4_pb.RollbackRequest
+
+    Returns:
+      a datastore_pb.Transaction
+    """
+    v3_txn = datastore_pb.Transaction()
+    self.v4_to_v3_txn(v4_req.transaction(), v3_txn)
+    return v3_txn
+
+  def v3_to_v4_rollback_req(self, v3_req):
+    """Converts a v3 Transaction to a v4 RollbackRequest.
+
+    Args:
+      v3_req: datastore_pb.Transaction
+
+    Returns:
+      a a datastore_v4_pb.RollbackRequest
+    """
+    v4_req = datastore_v4_pb.RollbackRequest()
+    v4_req.set_transaction(self._v3_to_v4_txn(v3_req))
+    return v4_req
+
+
+
+
+  def v4_commit_req_to_v3_txn(self, v4_req):
+    """Converts a v4 CommitRequest to a v3 Transaction.
+
+    Args:
+      v4_req: a datastore_v4_pb.CommitRequest
+
+    Returns:
+      a datastore_pb.Transaction
+    """
+    v3_txn = datastore_pb.Transaction()
+    self.v4_to_v3_txn(v4_req.transaction(), v3_txn)
+    return v3_txn
+
+
+
+
+  def v4_run_query_req_to_v3_query(self, v4_req):
+    """Converts a v4 RunQueryRequest to a v3 Query.
+
+    GQL is not supported.
+
+    Args:
+      v4_req: a datastore_v4_pb.RunQueryRequest
+
+    Returns:
+      a datastore_pb.Query
+    """
+
+    _check_conversion(not v4_req.has_gql_query(), 'GQL not supported')
+    v3_query = datastore_pb.Query()
+    self._query_converter.v4_to_v3_query(v4_req.partition_id(), v4_req.query(),
+                                         v3_query)
+
+
+    if v4_req.has_suggested_batch_size():
+      v3_query.set_count(v4_req.suggested_batch_size())
+
+
+    read_options = v4_req.read_options()
+    if read_options.has_transaction():
+      self.v4_to_v3_txn(read_options.transaction(),
+                        v3_query.mutable_transaction())
+    elif (read_options.read_consistency()
+          == datastore_v4_pb.ReadOptions.EVENTUAL):
+      v3_query.set_strong(False)
+      v3_query.set_failover_ms(-1)
+    elif read_options.read_consistency() == datastore_v4_pb.ReadOptions.STRONG:
+      v3_query.set_strong(True)
+
+    if v4_req.has_min_safe_time_seconds():
+      v3_query.set_min_safe_time_seconds(v4_req.min_safe_time_seconds())
+
+    return v3_query
+
+  def v3_to_v4_run_query_req(self, v3_req):
+    """Converts a v3 Query to a v4 RunQueryRequest.
+
+    Args:
+      v3_req: a datastore_pb.Query
+
+    Returns:
+      a datastore_v4_pb.RunQueryRequest
+    """
+    v4_req = datastore_v4_pb.RunQueryRequest()
+
+
+    v4_partition_id = v4_req.mutable_partition_id()
+    v4_partition_id.set_dataset_id(v3_req.app())
+    if v3_req.name_space():
+      v4_partition_id.set_namespace(v3_req.name_space())
+
+
+    if v3_req.has_count():
+      v4_req.set_suggested_batch_size(v3_req.count())
+
+
+    if v3_req.has_transaction():
+      v4_req.mutable_read_options().set_transaction(
+          self._v3_to_v4_txn(v3_req.transaction()))
+    elif v3_req.strong():
+      v4_req.mutable_read_options().set_read_consistency(
+          datastore_v4_pb.ReadOptions.STRONG)
+    elif v3_req.has_failover_ms():
+      v4_req.mutable_read_options().set_read_consistency(
+          datastore_v4_pb.ReadOptions.EVENTUAL)
+    if v3_req.has_min_safe_time_seconds():
+      v4_req.set_min_safe_time_seconds(v3_req.min_safe_time_seconds())
+
+    self._query_converter.v3_to_v4_query(v3_req, v4_req.mutable_query())
+
+    return v4_req
+
+  def v4_run_query_resp_to_v3_query_result(self, v4_resp):
+    """Converts a V4 RunQueryResponse to a v3 QueryResult.
+
+    Args:
+      v4_resp: a datastore_v4_pb.QueryResult
+
+    Returns:
+      a datastore_pb.QueryResult
+    """
+    v3_resp = self.v4_to_v3_query_result(v4_resp.batch())
+
+
+    if v4_resp.has_query_handle():
+      self.v4_to_v3_cursor(v4_resp.query_handle(), v3_resp.mutable_cursor())
+
+    return v3_resp
+
+  def v3_to_v4_run_query_resp(self, v3_resp):
+    """Converts a v3 QueryResult to a V4 RunQueryResponse.
+
+    Args:
+      v3_resp: a datastore_pb.QueryResult
+
+    Returns:
+      a datastore_v4_pb.RunQueryResponse
+    """
+    v4_resp = datastore_v4_pb.RunQueryResponse()
+    self.v3_to_v4_query_result_batch(v3_resp, v4_resp.mutable_batch())
+
+    if v3_resp.has_cursor():
+      v4_resp.set_query_handle(
+          self._query_converter.v3_to_v4_compiled_cursor(v3_resp.cursor()))
+
+    return v4_resp
+
+
+
+
+  def v4_to_v3_next_req(self, v4_req):
+    """Converts a v4 ContinueQueryRequest to a v3 NextRequest.
+
+    Args:
+      v4_req: a datastore_v4_pb.ContinueQueryRequest
+
+    Returns:
+      a datastore_pb.NextRequest
+    """
+    v3_req = datastore_pb.NextRequest()
+    v3_req.set_compile(True)
+    self.v4_to_v3_cursor(v4_req.query_handle(), v3_req.mutable_cursor())
+    return v3_req
+
+  def v3_to_v4_continue_query_resp(self, v3_resp):
+    """Converts a v3 QueryResult to a v4 ContinueQueryResponse.
+
+    Args:
+      v3_resp: a datstore_pb.QueryResult
+
+    Returns:
+      a datastore_v4_pb.ContinueQueryResponse
+    """
+    v4_resp = datastore_v4_pb.ContinueQueryResponse()
+    self.v3_to_v4_query_result_batch(v3_resp, v4_resp.mutable_batch())
+    return v4_resp
+
+
+
+
+  def v4_to_v3_get_req(self, v4_req):
+    """Converts a v4 LookupRequest to a v3 GetRequest.
+
+    Args:
+      v4_req: a datastore_v4_pb.LookupRequest
+
+    Returns:
+      a datastore_pb.GetRequest
+    """
+    v3_req = datastore_pb.GetRequest()
+    v3_req.set_allow_deferred(True)
+
+
+    if v4_req.read_options().has_transaction():
+      self.v4_to_v3_txn(v4_req.read_options().transaction(),
+                        v3_req.mutable_transaction())
+    elif (v4_req.read_options().read_consistency()
+          == datastore_v4_pb.ReadOptions.EVENTUAL):
+      v3_req.set_strong(False)
+      v3_req.set_failover_ms(-1)
+    elif (v4_req.read_options().read_consistency()
+          == datastore_v4_pb.ReadOptions.STRONG):
+      v3_req.set_strong(True)
+
+    for v4_key in v4_req.key_list():
+      self._entity_converter.v4_to_v3_reference(v4_key, v3_req.add_key())
+
+    return v3_req
+
+  def v3_to_v4_lookup_req(self, v3_req):
+    """Converts a v3 GetRequest to a v4 LookupRequest.
+
+    Args:
+      v3_req: a datastore_pb.GetRequest
+
+    Returns:
+      a datastore_v4_pb.LookupRequest
+    """
+    v4_req = datastore_v4_pb.LookupRequest()
+    _check_conversion(v3_req.allow_deferred(), 'allow_deferred must be true')
+
+
+    if v3_req.has_transaction():
+      v4_req.mutable_read_options().set_transaction(
+          self._v3_to_v4_txn(v3_req.transaction()))
+    elif v3_req.strong():
+      v4_req.mutable_read_options().set_read_consistency(
+          datastore_v4_pb.ReadOptions.STRONG)
+    elif v3_req.has_failover_ms():
+      v4_req.mutable_read_options().set_read_consistency(
+          datastore_v4_pb.ReadOptions.EVENTUAL)
+
+    for v3_ref in v3_req.key_list():
+      self._entity_converter.v3_to_v4_key(v3_ref, v4_req.add_key())
+
+    return v4_req
+
+  def v4_to_v3_get_resp(self, v4_resp):
+    """Converts a v4 LookupResponse to a v3 GetResponse.
+
+    Args:
+      v4_resp: a datastore_v4_pb.LookupResponse
+
+    Returns:
+      a datastore_pb.GetResponse
+    """
+    v3_resp = datastore_pb.GetResponse()
+
+    for v4_key in v4_resp.deferred_list():
+      self._entity_converter.v4_to_v3_reference(v4_key, v3_resp.add_deferred())
+    for v4_found in v4_resp.found_list():
+      self._entity_converter.v4_to_v3_entity(
+          v4_found.entity(), v3_resp.add_entity().mutable_entity())
+    for v4_missing in v4_resp.missing_list():
+      self._entity_converter.v4_to_v3_reference(
+          v4_missing.entity().key(),
+          v3_resp.add_entity().mutable_key())
+
+    return v3_resp
+
+  def v3_to_v4_lookup_resp(self, v3_resp):
+    """Converts a v3 GetResponse to a v4 LookupResponse.
+
+    Args:
+      v3_resp: a datastore_pb.GetResponse
+
+    Returns:
+      a datastore_v4_pb.LookupResponse
+    """
+    v4_resp = datastore_v4_pb.LookupResponse()
+
+    for v3_ref in v3_resp.deferred_list():
+      self._entity_converter.v3_to_v4_key(v3_ref, v4_resp.add_deferred())
+    for v3_entity in v3_resp.entity_list():
+      if v3_entity.has_entity():
+        self._entity_converter.v3_to_v4_entity(
+            v3_entity.entity(),
+            v4_resp.add_found().mutable_entity())
+      if v3_entity.has_key():
+        self._entity_converter.v3_to_v4_key(
+            v3_entity.key(),
+            v4_resp.add_missing().mutable_entity().mutable_key())
+
+    return v4_resp
+
+  def v4_to_v3_query_result(self, v4_batch):
+    """Converts a v4 QueryResultBatch to a v3 QueryResult.
+
+    Args:
+      v4_batch: a datastore_v4_pb.QueryResultBatch
+
+    Returns:
+      a datastore_pb.QueryResult
+    """
+    v3_result = datastore_pb.QueryResult()
+
+
+    v3_result.set_more_results(
+        (v4_batch.more_results()
+         == datastore_v4_pb.QueryResultBatch.NOT_FINISHED))
+    if v4_batch.has_end_cursor():
+      self._query_converter.v4_to_v3_compiled_cursor(
+          v4_batch.end_cursor(), v3_result.mutable_compiled_cursor())
+
+
+    if v4_batch.entity_result_type() == datastore_v4_pb.EntityResult.PROJECTION:
+      v3_result.set_index_only(True)
+    elif v4_batch.entity_result_type() == datastore_v4_pb.EntityResult.KEY_ONLY:
+      v3_result.set_keys_only(True)
+
+
+    if v4_batch.has_skipped_results():
+      v3_result.set_skipped_results(v4_batch.skipped_results())
+    for v4_entity in v4_batch.entity_result_list():
+      v3_entity = v3_result.add_result()
+      self._entity_converter.v4_to_v3_entity(v4_entity.entity(), v3_entity)
+      if v4_batch.entity_result_type() != datastore_v4_pb.EntityResult.FULL:
+
+
+        v3_entity.clear_entity_group()
+
+    return v3_result
+
+  def v3_to_v4_query_result_batch(self, v3_result, v4_batch):
+    """Converts a v3 QueryResult to a v4 QueryResultBatch.
+
+    Args:
+      v3_result: a datastore_pb.QueryResult
+      v4_batch: a datastore_v4_pb.QueryResultBatch to populate
+    """
+    v4_batch.Clear()
+
+
+    if v3_result.more_results():
+      v4_batch.set_more_results(datastore_v4_pb.QueryResultBatch.NOT_FINISHED)
+    else:
+      v4_batch.set_more_results(
+          datastore_v4_pb.QueryResultBatch.MORE_RESULTS_AFTER_LIMIT)
+    if v3_result.has_compiled_cursor():
+      v4_batch.set_end_cursor(
+          self._query_converter.v3_to_v4_compiled_cursor(
+              v3_result.compiled_cursor()))
+
+
+    if v3_result.keys_only():
+      v4_batch.set_entity_result_type(datastore_v4_pb.EntityResult.KEY_ONLY)
+    elif v3_result.index_only():
+      v4_batch.set_entity_result_type(datastore_v4_pb.EntityResult.PROJECTION)
+    else:
+      v4_batch.set_entity_result_type(datastore_v4_pb.EntityResult.FULL)
+
+
+    if v3_result.has_skipped_results():
+      v4_batch.set_skipped_results(v3_result.skipped_results())
+    for v3_entity in v3_result.result_list():
+      v4_entity_result = datastore_v4_pb.EntityResult()
+      self._entity_converter.v3_to_v4_entity(v3_entity,
+                                             v4_entity_result.mutable_entity())
+      v4_batch.entity_result_list().append(v4_entity_result)
+
+
+class _StubServiceConverter(_BaseServiceConverter):
+  """Converter for request/response protos suitable for use in stubs."""
+
+  def v4_to_v3_cursor(self, v4_query_handle, v3_cursor):
+    v3_cursor.ParseFromString(v4_query_handle)
+    return v3_cursor
+
+  def _v3_to_v4_query_handle(self, v3_cursor):
+    return v3_cursor.SerializeToString()
+
+  def v4_to_v3_txn(self, v4_txn, v3_txn):
+    v3_txn.ParseFromString(v4_txn)
+    return v3_txn
+
+  def _v3_to_v4_txn(self, v3_txn):
+    return v3_txn.SerializeToString()
+
+
+
+__stub_service_converter = _StubServiceConverter(__entity_converter,
+                                                 __stub_query_converter)
+
+
+def get_stub_service_converter():
+  """Returns a converter for v3 and v4 service request/response protos.
+
+  This converter is suitable for use in stubs but not for production.
+
+  Returns:
+    a _StubServiceConverter
+  """
+  return __stub_service_converter
diff --git a/google/appengine/datastore/datastore_rpc.py b/google/appengine/datastore/datastore_rpc.py
index c60f60f..ac1dbb5 100644
--- a/google/appengine/datastore/datastore_rpc.py
+++ b/google/appengine/datastore/datastore_rpc.py
@@ -67,8 +67,8 @@
 
 from google.appengine.api.app_identity import app_identity
 from google.appengine.datastore import datastore_pb
+from google.appengine.datastore import datastore_pbs
 from google.appengine.datastore import datastore_v4_pb
-from google.appengine.datastore import entity_v4_pb
 from google.appengine.runtime import apiproxy_errors
 
 
@@ -1764,38 +1764,6 @@
 
 
 
-  def __to_v4_key(self, ref):
-    """Convert a valid v3 Reference pb to a v4 Key pb."""
-    key = entity_v4_pb.Key()
-    key.mutable_partition_id().set_dataset_id(ref.app())
-    if ref.name_space():
-      key.mutable_partition_id().set_namespace(ref.name_space())
-    for el_v3 in ref.path().element_list():
-      el_v4 = key.add_path_element()
-      el_v4.set_kind(el_v3.type())
-      if el_v3.has_id():
-        el_v4.set_id(el_v3.id())
-      if el_v3.has_name():
-        el_v4.set_name(el_v3.name())
-    return key
-
-  def __to_v3_reference(self, key):
-    """Convert a valid v4 Key pb to a v3 Reference pb."""
-    ref = entity_pb.Reference()
-    ref.set_app(key.partition_id().dataset_id())
-    if key.partition_id().has_namespace():
-      ref.set_name_space(key.partition_id().namespace())
-    for el_v4 in key.path_element_list():
-      el_v3 = ref.mutable_path().add_element()
-      el_v3.set_type(el_v4.kind())
-      if el_v4.has_id():
-        el_v3.set_id(el_v4.id())
-      if el_v4.has_name():
-        el_v3.set_name(el_v4.name())
-    return ref
-
-
-
   def allocate_ids(self, key, size=None, max=None):
     """Synchronous AllocateIds operation.
 
@@ -1909,7 +1877,9 @@
     pbsgen = self._generate_pb_lists(keys_by_idkey, 0, max_count, None, config)
     for pbs, _ in pbsgen:
       req = datastore_v4_pb.AllocateIdsRequest()
-      req.reserve_list().extend([self.__to_v4_key(key) for key in pbs])
+      for key in pbs:
+        datastore_pbs.get_entity_converter().v3_to_v4_key(key,
+                                                          req.add_reserve())
       resp = datastore_v4_pb.AllocateIdsResponse()
       rpcs.append(self.make_rpc_call(config, 'AllocateIds', req, resp,
                                      self.__reserve_keys_hook, extra_hook,
diff --git a/google/appengine/datastore/datastore_sqlite_stub.py b/google/appengine/datastore/datastore_sqlite_stub.py
index 1da2b9a..3b10bf6 100644
--- a/google/appengine/datastore/datastore_sqlite_stub.py
+++ b/google/appengine/datastore/datastore_sqlite_stub.py
@@ -1101,7 +1101,8 @@
         prefix,
         self._CreateFilterString(filters, params),
         self.__CreateOrderString(orders))
-    query = ('SELECT Entities.__path__, Entities.entity '
+    query = ('SELECT Entities.__path__, Entities.entity, '
+             'EntitiesByProperty.name, EntitiesByProperty.value '
              'FROM "%s!EntitiesByProperty" AS EntitiesByProperty INNER JOIN '
              '"%s!Entities" AS Entities USING (__path__) %s %s' % format_args)
     return query, params
diff --git a/google/appengine/datastore/datastore_stub_util.py b/google/appengine/datastore/datastore_stub_util.py
index 7cd736b..05d903f 100644
--- a/google/appengine/datastore/datastore_stub_util.py
+++ b/google/appengine/datastore/datastore_stub_util.py
@@ -325,6 +325,22 @@
     raise apiproxy_errors.ApplicationError(error_code, msg)
 
 
+def CheckValidUTF8(string, desc):
+  """Check that the given string is valid UTF-8.
+
+  Args:
+    string: the string to validate.
+    desc: a description of the string being validated.
+
+  Raises:
+    apiproxy_errors.ApplicationError: if the string is not valid UTF-8.
+  """
+  try:
+    string.decode('utf-8')
+  except UnicodeDecodeError:
+    Check(False, '%s is not valid UTF-8.' % desc)
+
+
 def CheckAppId(request_trusted, request_app_id, app_id):
   """Check that this is the stub for app_id.
 
@@ -376,6 +392,9 @@
   for elem in key.path().element_list():
     Check(not elem.has_id() or not elem.has_name(),
           'each key path element should have id or name but not both: %r' % key)
+    CheckValidUTF8(elem.type(), 'key path element type')
+    if elem.has_name():
+      CheckValidUTF8(elem.name(), 'key path element name')
 
 
 def CheckEntity(request_trusted, request_app_id, entity):
@@ -413,6 +432,7 @@
   name = prop.name()
   value = prop.value()
   meaning = prop.meaning()
+  CheckValidUTF8(name, 'property name')
   Check(request_trusted or
         not datastore_types.RESERVED_PROPERTY_NAME.match(name),
         'cannot store entity with reserved property name \'%s\'' % name)
@@ -433,16 +453,17 @@
   if meaning == entity_pb.Property.ATOM_LINK:
     max_length = datastore_types._MAX_LINK_PROPERTY_LENGTH
 
-  CheckPropertyValue(name, value, max_length)
+  CheckPropertyValue(name, value, max_length, meaning)
 
 
-def CheckPropertyValue(name, value, max_length):
+def CheckPropertyValue(name, value, max_length, meaning):
   """Check if this property value can be stored.
 
   Args:
     name: name of the property
     value: entity_pb.PropertyValue
     max_length: maximum length for string values
+    meaning: meaning of the property
 
   Raises:
     apiproxy_errors.ApplicationError: if the property is invalid
@@ -469,6 +490,9 @@
 
     Check((len(s16) - 2) / 2 <= max_length,
           'Property %s is too long. Maximum length is %d.' % (name, max_length))
+    if (meaning not in _BLOB_MEANINGS and
+        meaning != entity_pb.Property.BYTESTRING):
+      CheckValidUTF8(value.stringvalue(), 'String property value')
 
 
 def CheckTransaction(request_trusted, request_app_id, transaction):
@@ -1032,13 +1056,19 @@
     cursor_entity = entity_pb.EntityProto()
     if position.has_key():
       cursor_entity.mutable_key().CopyFrom(position.key())
-      remaining_properties.remove('__key__')
+      try:
+        remaining_properties.remove('__key__')
+      except KeyError:
+        Check(False, 'Cursor does not match query: extra value __key__')
     for indexvalue in position.indexvalue_list():
       property = cursor_entity.add_property()
       property.set_name(indexvalue.property())
       property.mutable_value().CopyFrom(indexvalue.value())
-      remaining_properties.remove(indexvalue.property())
-
+      try:
+        remaining_properties.remove(indexvalue.property())
+      except KeyError:
+        Check(False, 'Cursor does not match query: extra value %s' %
+              indexvalue.property())
     Check(not remaining_properties,
           'Cursor does not match query: missing values for %r' %
           remaining_properties)
@@ -3267,7 +3297,7 @@
 
   if not orders:
     for filter_pb in filters:
-      if filter_pb.op() != datastore_pb.Query_Filter.EQUAL:
+      if filter_pb.op() in datastore_index.INEQUALITY_OPERATORS:
 
         order = datastore_pb.Query_Order()
         order.set_property(filter_pb.property(0).name())
diff --git a/google/appengine/ext/analytics/static/analytics_js.js b/google/appengine/ext/analytics/static/analytics_js.js
index aca2d10..596ec3b 100644
--- a/google/appengine/ext/analytics/static/analytics_js.js
+++ b/google/appengine/ext/analytics/static/analytics_js.js
@@ -1,22 +1,25 @@
-/* Copyright 2008-9 Google Inc. All Rights Reserved. */ (function(){var k,m=this,n=function(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
-else if("function"==b&&"undefined"==typeof a.call)return"object";return b},q=function(a){return"string"==typeof a},r="closure_uid_"+(1E9*Math.random()>>>0),t=0,u=function(a,b){var c=Array.prototype.slice.call(arguments,1);return function(){var b=c.slice();b.push.apply(b,arguments);return a.apply(this,b)}},v=function(a,b){var c=a.split("."),e=m;c[0]in e||!e.execScript||e.execScript("var "+c[0]);for(var d;c.length&&(d=c.shift());)c.length||void 0===b?e=e[d]?e[d]:e[d]={}:e[d]=b},w=function(a,b){function c(){}
-c.prototype=b.prototype;a.l=b.prototype;a.prototype=new c};var y=function(a){Error.captureStackTrace?Error.captureStackTrace(this,y):this.stack=Error().stack||"";a&&(this.message=String(a))};w(y,Error);var aa=function(a,b){for(var c=a.split("%s"),e="",d=Array.prototype.slice.call(arguments,1);d.length&&1<c.length;)e+=c.shift()+d.shift();return e+c.join("%s")};var z=function(a,b){b.unshift(a);y.call(this,aa.apply(null,b));b.shift()};w(z,y);var A=function(a,b,c){if(!a){var e=Array.prototype.slice.call(arguments,2),d="Assertion failed";if(b)var d=d+(": "+b),f=e;throw new z(""+d,f||[]);}};var B=Array.prototype,C=B.indexOf?function(a,b,c){A(null!=a.length);return B.indexOf.call(a,b,c)}:function(a,b,c){c=null==c?0:0>c?Math.max(0,a.length+c):c;if(q(a))return q(b)&&1==b.length?a.indexOf(b,c):-1;for(;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},ba=B.forEach?function(a,b,c){A(null!=a.length);B.forEach.call(a,b,c)}:function(a,b,c){for(var e=a.length,d=q(a)?a.split(""):a,f=0;f<e;f++)f in d&&b.call(c,d[f],f,a)},ca=B.filter?function(a,b,c){A(null!=a.length);return B.filter.call(a,b,
-c)}:function(a,b,c){for(var e=a.length,d=[],f=0,g=q(a)?a.split(""):a,h=0;h<e;h++)if(h in g){var x=g[h];b.call(c,x,h,a)&&(d[f++]=x)}return d},D=function(a){var b=a.length;if(0<b){for(var c=Array(b),e=0;e<b;e++)c[e]=a[e];return c}return[]},E=function(a,b,c){A(null!=a.length);return 2>=arguments.length?B.slice.call(a,b):B.slice.call(a,b,c)};var F,G,H,I,J=function(){return m.navigator?m.navigator.userAgent:null};I=H=G=F=!1;var K;if(K=J()){var da=m.navigator;F=0==K.lastIndexOf("Opera",0);G=!F&&(-1!=K.indexOf("MSIE")||-1!=K.indexOf("Trident"));H=!F&&-1!=K.indexOf("WebKit");I=!F&&!H&&!G&&"Gecko"==da.product}var L=F,M=G,N=I,O=H,ea=m.navigator,fa=-1!=(ea&&ea.platform||"").indexOf("Mac"),ga=function(){var a=m.document;return a?a.documentMode:void 0},P;
-n:{var Q="",R;if(L&&m.opera)var S=m.opera.version,Q="function"==typeof S?S():S;else if(N?R=/rv\:([^\);]+)(\)|;)/:M?R=/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/:O&&(R=/WebKit\/(\S+)/),R)var ha=R.exec(J()),Q=ha?ha[1]:"";if(M){var ia=ga();if(ia>parseFloat(Q)){P=String(ia);break n}}P=Q}
-var ja=P,ka={},T=function(a){var b;if(!(b=ka[a])){b=0;for(var c=String(ja).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),e=String(a).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),d=Math.max(c.length,e.length),f=0;0==b&&f<d;f++){var g=c[f]||"",h=e[f]||"",x=RegExp("(\\d*)(\\D*)","g"),p=RegExp("(\\d*)(\\D*)","g");do{var l=x.exec(g)||["","",""],s=p.exec(h)||["","",""];if(0==l[0].length&&0==s[0].length)break;b=((0==l[1].length?0:parseInt(l[1],10))<(0==s[1].length?0:parseInt(s[1],10))?-1:(0==l[1].length?
-0:parseInt(l[1],10))>(0==s[1].length?0:parseInt(s[1],10))?1:0)||((0==l[2].length)<(0==s[2].length)?-1:(0==l[2].length)>(0==s[2].length)?1:0)||(l[2]<s[2]?-1:l[2]>s[2]?1:0)}while(0==b)}b=ka[a]=0<=b}return b},la=m.document,ma=la&&M?ga()||("CSS1Compat"==la.compatMode?parseInt(ja,10):5):void 0;!N&&!M||M&&M&&9<=ma||N&&T("1.9.1");M&&T("9");var na=function(a){a=a.className;return q(a)&&a.match(/\S+/g)||[]},oa=function(a,b){for(var c=na(a),e=E(arguments,1),d=c,f=0;f<e.length;f++)0<=C(d,e[f])||d.push(e[f]);c=c.join(" ");a.className=c},qa=function(a,b){var c=na(a),e=E(arguments,1),c=pa(c,e).join(" ");a.className=c},pa=function(a,b){return ca(a,function(a){return!(0<=C(b,a))})};var U=function(a,b,c){var e=document;c=c||e;a=a&&"*"!=a?a.toUpperCase():"";if(c.querySelectorAll&&c.querySelector&&(a||b))return c.querySelectorAll(a+(b?"."+b:""));if(b&&c.getElementsByClassName){c=c.getElementsByClassName(b);if(a){for(var e={},d=0,f=0,g;g=c[f];f++)a==g.nodeName&&(e[d++]=g);e.length=d;return e}return c}c=c.getElementsByTagName(a||"*");if(b){e={};for(f=d=0;g=c[f];f++)a=g.className,"function"==typeof a.split&&0<=C(a.split(/\s+/),b)&&(e[d++]=g);e.length=d;return e}return c};var V=function(a){V[" "](a);return a};V[" "]=function(){};var ra=!M||M&&9<=ma,sa=M&&!T("9");!O||T("528");N&&T("1.9b")||M&&T("8")||L&&T("9.5")||O&&T("528");N&&!T("8")||M&&T("9");var W=function(a,b){this.type=a;this.currentTarget=this.target=b};W.prototype.h=!1;W.prototype.defaultPrevented=!1;W.prototype.o=!0;W.prototype.preventDefault=function(){this.defaultPrevented=!0;this.o=!1};var X=function(a,b){if(a){var c=this.type=a.type;W.call(this,c);this.target=a.target||a.srcElement;this.currentTarget=b;var e=a.relatedTarget;if(e){if(N){var d;n:{try{V(e.nodeName);d=!0;break n}catch(f){}d=!1}d||(e=null)}}else"mouseover"==c?e=a.fromElement:"mouseout"==c&&(e=a.toElement);this.relatedTarget=e;this.offsetX=O||void 0!==a.offsetX?a.offsetX:a.layerX;this.offsetY=O||void 0!==a.offsetY?a.offsetY:a.layerY;this.clientX=void 0!==a.clientX?a.clientX:a.pageX;this.clientY=void 0!==a.clientY?a.clientY:
-a.pageY;this.screenX=a.screenX||0;this.screenY=a.screenY||0;this.button=a.button;this.keyCode=a.keyCode||0;this.charCode=a.charCode||("keypress"==c?a.keyCode:0);this.ctrlKey=a.ctrlKey;this.altKey=a.altKey;this.shiftKey=a.shiftKey;this.metaKey=a.metaKey;this.k=fa?a.metaKey:a.ctrlKey;this.state=a.state;this.i=a;a.defaultPrevented&&this.preventDefault();delete this.h}};w(X,W);k=X.prototype;k.target=null;k.relatedTarget=null;k.offsetX=0;k.offsetY=0;k.clientX=0;k.clientY=0;k.screenX=0;k.screenY=0;
-k.button=0;k.keyCode=0;k.charCode=0;k.ctrlKey=!1;k.altKey=!1;k.shiftKey=!1;k.metaKey=!1;k.k=!1;k.i=null;k.preventDefault=function(){X.l.preventDefault.call(this);var a=this.i;if(a.preventDefault)a.preventDefault();else if(a.returnValue=!1,sa)try{if(a.ctrlKey||112<=a.keyCode&&123>=a.keyCode)a.keyCode=-1}catch(b){}};var ta="closure_listenable_"+(1E6*Math.random()|0),ua=0;var va=function(a,b,c,e,d){this.b=a;this.d=null;this.src=b;this.type=c;this.capture=!!e;this.e=d;this.key=++ua;this.c=this.f=!1},wa=function(a){a.c=!0;a.b=null;a.d=null;a.src=null;a.e=null};var xa=function(a){this.src=a;this.a={};this.g=0};xa.prototype.add=function(a,b,c,e,d){var f=this.a[a];f||(f=this.a[a]=[],this.g++);var g;n:{for(g=0;g<f.length;++g){var h=f[g];if(!h.c&&h.b==b&&h.capture==!!e&&h.e==d)break n}g=-1}-1<g?(a=f[g],c||(a.f=!1)):(a=new va(b,this.src,a,!!e,d),a.f=c,f.push(a));return a};var ya=function(a,b){var c=b.type;if(c in a.a){var e=a.a[c],d=C(e,b),f;if(f=0<=d)A(null!=e.length),B.splice.call(e,d,1);f&&(wa(b),0==a.a[c].length&&(delete a.a[c],a.g--))}};var za={},Y={},Z={},Ba=function(){var a=Aa,b=ra?function(c){return a.call(b.src,b.b,c)}:function(c){c=a.call(b.src,b.b,c);if(!c)return c};return b},Ca=function(a,b,c,e,d){if("array"==n(b))for(var f=0;f<b.length;f++)Ca(a,b[f],c,e,d);else if(c=Da(c),a&&a[ta])a.j.add(b,c,!0,e,d);else{if(!b)throw Error("Invalid event type");var f=!!e,g=a[r]||(a[r]=++t),h=Y[g];h||(Y[g]=h=new xa(a));c=h.add(b,c,!0,e,d);c.d||(e=Ba(),c.d=e,e.src=a,e.b=c,a.addEventListener?a.addEventListener(b,e,f):a.attachEvent(b in Z?Z[b]:
-Z[b]="on"+b,e),za[c.key]=c)}},Ga=function(a,b,c,e){var d=1;if(a=Ea(a))if(b=a.a[b])for(b=D(b),a=0;a<b.length;a++){var f=b[a];f&&(f.capture==c&&!f.c)&&(d&=!1!==Fa(f,e))}return Boolean(d)},Fa=function(a,b){var c=a.b,e=a.e||a.src;if(a.f&&"number"!=typeof a&&a&&!a.c){var d=a.src;if(d&&d[ta])ya(d.j,a);else{var f=a.type,g=a.d;d.removeEventListener?d.removeEventListener(f,g,a.capture):d.detachEvent&&d.detachEvent(f in Z?Z[f]:Z[f]="on"+f,g);(f=Ea(d))?(ya(f,a),0==f.g&&(f.src=null,delete Y[d[r]||(d[r]=++t)])):
-wa(a);delete za[a.key]}}return c.call(e,b)},Aa=function(a,b){if(a.c)return!0;if(!ra){var c;if(!(c=b))n:{c=["window","event"];for(var e=m,d;d=c.shift();)if(null!=e[d])e=e[d];else{c=null;break n}c=e}d=c;c=new X(d,this);e=!0;if(!(0>d.keyCode||void 0!=d.returnValue)){n:{var f=!1;if(0==d.keyCode)try{d.keyCode=-1;break n}catch(g){f=!0}if(f||void 0==d.returnValue)d.returnValue=!0}d=[];for(f=c.currentTarget;f;f=f.parentNode)d.push(f);for(var f=a.type,h=d.length-1;!c.h&&0<=h;h--)c.currentTarget=d[h],e&=Ga(d[h],
-f,!0,c);for(h=0;!c.h&&h<d.length;h++)c.currentTarget=d[h],e&=Ga(d[h],f,!1,c)}return e}return Fa(a,new X(b,this))},Ea=function(a){return a[r]?Y[a[r]||(a[r]=++t)]||null:null},Ha="__closure_events_fn_"+(1E9*Math.random()>>>0),Da=function(a){A(a,"Listener can not be null.");if("function"==n(a))return a;A(a.handleEvent,"An object listener must have handleEvent method.");return a[Ha]||(a[Ha]=function(b){return a.handleEvent(b)})};var $=function(){};$.m=function(){$.n||($.n=new $)};$.m();M||O&&T("525");v("ae.init",function(){Ia();Ja();Ca(window,"load",function(){});Ka()});
-var Ia=function(){var a;a=document;if(a=q("ae-content")?a.getElementById("ae-content"):"ae-content"){a=U("table","ae-table-striped",a);for(var b=0,c;c=a[b];b++){c=U("tbody",null,c);for(var e=0,d;d=c[e];e++){d=U("tr",null,d);for(var f=0,g;g=d[f];f++)f%2&&oa(g,"ae-even")}}}},Ja=function(){var a=U(null,"ae-noscript",void 0);ba(D(a),function(a){qa(a,"ae-noscript")})},Ka=function(){m._gaq=m._gaq||[];m._gaq.push(function(){m._gaq._createAsyncTracker("UA-3739047-3","ae")._trackPageview()});(function(){var a=
-document.createElement("script");a.src=("https:"==document.location.protocol?"https://ssl":"http://www")+".google-analytics.com/ga.js";a.setAttribute("async","true");document.documentElement.firstChild.appendChild(a)})()};v("ae.trackPageView",function(){m._gaq&&m._gaq._getAsyncTracker("ae")._trackPageview()});var Ma=function(a){if(void 0==a||null==a||0==a.length)return 0;a=Math.max.apply(Math,a);return La(a)},La=function(a){var b=5;2>b&&(b=2);b-=1;return Math.ceil(a/b)*b},Na=function(a,b,c){a=a.getSelection();1==a.length&&(a=a[0],null!=a.row&&(null!=b.starttime&&(c+="&starttime="+b.starttime),null!=b.endtime&&(c+="&endtime="+b.endtime),null!=b.latency_lower&&(c+="&latency_lower="+b.latency_lower),null!=b.latency_upper&&(c+="&latency_upper="+b.latency_upper),b=c+"&detail="+a.row,window.location.href=b))},
-Oa=function(a,b,c,e,d){var f=new google.visualization.DataTable;f.addColumn("string","");f.addColumn("number","");f.addColumn({type:"string",role:"tooltip"});for(var g=0;g<b.length;g++)f.addRow(["",b[g],c[g]]);c=Math.max(10*b.length,200);b=Ma(b);a=new google.visualization.ColumnChart(document.getElementById("rpctime-"+a));a.draw(f,{height:100,width:c,legend:"none",chartArea:{left:40},fontSize:11,vAxis:{minValue:0,maxValue:b,gridlines:{count:5}}});google.visualization.events.addListener(a,"select",
-u(Na,a,e,d))};v("ae.Charts.latencyHistogram",function(a,b,c){var e=new google.visualization.DataTable;e.addColumn("string","");e.addColumn("number","");for(var d=0;d<b.length;d++)e.addRow([""+a[d],b[d]]);for(d=b.length;d<a.length;d++)e.addRow([""+a[d],0]);b=Ma(b);(new google.visualization.ColumnChart(document.getElementById("latency-"+c))).draw(e,{legend:"none",width:20*a.length,height:200,vAxis:{maxValue:b,gridlines:{count:5}}})});
-v("ae.Charts.latencyTimestampScatter",function(a,b,c,e,d){var f=new google.visualization.DataTable;f.addColumn("number","Time (seconds from start)");f.addColumn("number","Latency");for(var g=0;g<a.length;g++){var h=Math.round(a[g]-c);f.addRow([h,b[g]])}a=e.starttime?e.starttime:0;b=new google.visualization.ScatterChart(document.getElementById("LatencyVsTimestamp"));b.draw(f,{hAxis:{title:"Time (seconds from start of recording)",minValue:a},vAxis:{title:"Request Latency (milliseconds)",minValue:0},
-tooltip:{trigger:"none"},legend:"none"});google.visualization.events.addListener(b,"select",u(Na,b,e,d))});
-v("ae.Charts.entityCountBarChart",function(a,b,c,e){var d=new google.visualization.DataTable;d.addColumn("string","");d.addColumn("number","Reads");d.addColumn({type:"string",role:"tooltip"});d.addColumn("number","Misses");d.addColumn({type:"string",role:"tooltip"});d.addColumn("number","Writes");d.addColumn({type:"string",role:"tooltip"});var f=50;f>b.length&&(f=b.length);for(var g=0;g<f;g++)d.addRow(["",b[g][1]-b[g][3],b[g][0],b[g][3],b[g][0],b[g][2],b[g][0]]);b=20*f;f=b+130;a=new google.visualization.ColumnChart(document.getElementById(e+
-"-"+a));c=La(c);a.draw(d,{height:100,width:f,chartArea:{width:b},fontSize:10,isStacked:!0,vAxis:{minValue:0,maxValue:c,gridlines:{count:5}}})});
-v("ae.Charts.rpcVariationCandlestick",function(a){var b=new google.visualization.DataTable;b.addColumn("string","");b.addColumn("number","");b.addColumn("number","");b.addColumn("number","");b.addColumn("number","");b.addRows(a);(new google.visualization.CandlestickChart(document.getElementById("rpcvariation"))).draw(b,{vAxis:{title:"RPC Latency variation (milliseconds)"},hAxis:{textPosition:"out",slantedText:!0,slantedTextAngle:45,textStyle:{fontSize:13}},height:250,chartArea:{top:10,height:100},
-legend:"none",tooltip:{trigger:"none"}})});v("ae.Charts.totalTimeBarChart",function(a,b,c,e){for(var d=[],f=0;f<b.length;f++)d[f]=b[f]+" milliseconds";Oa(a,b,d,c,e)});v("ae.Charts.rpcTimeBarChart",function(a,b,c,e,d){var f=[],g=[],h=c.indices,x=c.times;c=c.stats;for(var p=0;p<b;p++)f[p]=0,g[p]=null;for(p=0;p<h.length;p++){f[h[p]]=x[p];b=c[p];var l="Calls: "+b[0];if(0<b[1]||0<b[2]||0<b[3])l+=" Entities";0<b[1]&&(l+=" R:"+b[1]);0<b[2]&&(l+=" W:"+b[2]);0<b[3]&&(l+=" M:"+b[3]);g[h[p]]=l}Oa(a,f,g,e,d)});})();
+/* Copyright 2008-9 Google Inc. All Rights Reserved. */ (function(){var h,m=this,n=function(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";
+else if("function"==b&&"undefined"==typeof a.call)return"object";return b},q=function(a){return"string"==typeof a},r=function(a,b){var c=Array.prototype.slice.call(arguments,1);return function(){var b=c.slice();b.push.apply(b,arguments);return a.apply(this,b)}},aa=Date.now||function(){return+new Date},t=function(a,b){var c=a.split("."),d=m;c[0]in d||!d.execScript||d.execScript("var "+c[0]);for(var e;c.length&&(e=c.shift());)c.length||void 0===b?d=d[e]?d[e]:d[e]={}:d[e]=b},u=function(a,b){function c(){}
+c.prototype=b.prototype;a.o=b.prototype;a.prototype=new c};var v=function(a){Error.captureStackTrace?Error.captureStackTrace(this,v):this.stack=Error().stack||"";a&&(this.message=String(a))};u(v,Error);var ba=function(a,b){for(var c=a.split("%s"),d="",e=Array.prototype.slice.call(arguments,1);e.length&&1<c.length;)d+=c.shift()+e.shift();return d+c.join("%s")},x=function(a){a=String(a);var b=a.indexOf(".");-1==b&&(b=a.length);b=Math.max(0,2-b);return Array(b+1).join("0")+a};var y=function(a,b){b.unshift(a);v.call(this,ba.apply(null,b));b.shift()};u(y,v);var z=function(a,b,c){if(!a){var d=Array.prototype.slice.call(arguments,2),e="Assertion failed";if(b)var e=e+(": "+b),f=d;throw new y(""+e,f||[]);}};var A=Array.prototype,B=A.indexOf?function(a,b,c){z(null!=a.length);return A.indexOf.call(a,b,c)}:function(a,b,c){c=null==c?0:0>c?Math.max(0,a.length+c):c;if(q(a))return q(b)&&1==b.length?a.indexOf(b,c):-1;for(;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},ca=A.forEach?function(a,b,c){z(null!=a.length);A.forEach.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=q(a)?a.split(""):a,f=0;f<d;f++)f in e&&b.call(c,e[f],f,a)},da=A.filter?function(a,b,c){z(null!=a.length);return A.filter.call(a,b,
+c)}:function(a,b,c){for(var d=a.length,e=[],f=0,g=q(a)?a.split(""):a,k=0;k<d;k++)if(k in g){var w=g[k];b.call(c,w,k,a)&&(e[f++]=w)}return e},C=function(a){var b=a.length;if(0<b){for(var c=Array(b),d=0;d<b;d++)c[d]=a[d];return c}return[]},ea=function(a,b,c){z(null!=a.length);return 2>=arguments.length?A.slice.call(a,b):A.slice.call(a,b,c)};var D,E,F,G,fa=function(){return m.navigator?m.navigator.userAgent:null};G=F=E=D=!1;var H;if(H=fa()){var ga=m.navigator;D=0==H.lastIndexOf("Opera",0);E=!D&&(-1!=H.indexOf("MSIE")||-1!=H.indexOf("Trident"));F=!D&&-1!=H.indexOf("WebKit");G=!D&&!F&&!E&&"Gecko"==ga.product}var ha=D,I=E,J=G,K=F,ia=function(){var a=m.document;return a?a.documentMode:void 0},L;
+n:{var M="",N;if(ha&&m.opera)var O=m.opera.version,M="function"==typeof O?O():O;else if(J?N=/rv\:([^\);]+)(\)|;)/:I?N=/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/:K&&(N=/WebKit\/(\S+)/),N)var ja=N.exec(fa()),M=ja?ja[1]:"";if(I){var ka=ia();if(ka>parseFloat(M)){L=String(ka);break n}}L=M}
+var la=L,ma={},P=function(a){var b;if(!(b=ma[a])){b=0;for(var c=String(la).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),d=String(a).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),e=Math.max(c.length,d.length),f=0;0==b&&f<e;f++){var g=c[f]||"",k=d[f]||"",w=RegExp("(\\d*)(\\D*)","g"),p=RegExp("(\\d*)(\\D*)","g");do{var l=w.exec(g)||["","",""],s=p.exec(k)||["","",""];if(0==l[0].length&&0==s[0].length)break;b=((0==l[1].length?0:parseInt(l[1],10))<(0==s[1].length?0:parseInt(s[1],10))?-1:(0==l[1].length?
+0:parseInt(l[1],10))>(0==s[1].length?0:parseInt(s[1],10))?1:0)||((0==l[2].length)<(0==s[2].length)?-1:(0==l[2].length)>(0==s[2].length)?1:0)||(l[2]<s[2]?-1:l[2]>s[2]?1:0)}while(0==b)}b=ma[a]=0<=b}return b},na=m.document,oa=na&&I?ia()||("CSS1Compat"==na.compatMode?parseInt(la,10):5):void 0;!J&&!I||I&&I&&9<=oa||J&&P("1.9.1");I&&P("9");var pa=function(a){a=a.className;return q(a)&&a.match(/\S+/g)||[]},qa=function(a,b){for(var c=pa(a),d=ea(arguments,1),e=c,f=0;f<d.length;f++)0<=B(e,d[f])||e.push(d[f]);c=c.join(" ");a.className=c},sa=function(a,b){var c=pa(a),d=ea(arguments,1),c=ra(c,d).join(" ");a.className=c},ra=function(a,b){return da(a,function(a){return!(0<=B(b,a))})};var Q=function(a,b,c){var d=document;c=c||d;a=a&&"*"!=a?a.toUpperCase():"";if(c.querySelectorAll&&c.querySelector&&(a||b))return c.querySelectorAll(a+(b?"."+b:""));if(b&&c.getElementsByClassName){c=c.getElementsByClassName(b);if(a){for(var d={},e=0,f=0,g;g=c[f];f++)a==g.nodeName&&(d[e++]=g);d.length=e;return d}return c}c=c.getElementsByTagName(a||"*");if(b){d={};for(f=e=0;g=c[f];f++)a=g.className,"function"==typeof a.split&&0<=B(a.split(/\s+/),b)&&(d[e++]=g);d.length=e;return d}return c};var R=function(a){R[" "](a);return a};R[" "]=function(){};var ta=!I||I&&9<=oa,ua=I&&!P("9");!K||P("528");J&&P("1.9b")||I&&P("8")||ha&&P("9.5")||K&&P("528");J&&!P("8")||I&&P("9");var S=function(a,b){this.type=a;this.currentTarget=this.target=b};S.prototype.i=!1;S.prototype.defaultPrevented=!1;S.prototype.preventDefault=function(){this.defaultPrevented=!0};var T=function(a,b){if(a){var c=this.type=a.type;S.call(this,c);this.target=a.target||a.srcElement;this.currentTarget=b;var d=a.relatedTarget;if(d){if(J){var e;n:{try{R(d.nodeName);e=!0;break n}catch(f){}e=!1}e||(d=null)}}else"mouseover"==c?d=a.fromElement:"mouseout"==c&&(d=a.toElement);this.relatedTarget=d;this.offsetX=K||void 0!==a.offsetX?a.offsetX:a.layerX;this.offsetY=K||void 0!==a.offsetY?a.offsetY:a.layerY;this.clientX=void 0!==a.clientX?a.clientX:a.pageX;this.clientY=void 0!==a.clientY?a.clientY:
+a.pageY;this.screenX=a.screenX||0;this.screenY=a.screenY||0;this.button=a.button;this.keyCode=a.keyCode||0;this.charCode=a.charCode||("keypress"==c?a.keyCode:0);this.ctrlKey=a.ctrlKey;this.altKey=a.altKey;this.shiftKey=a.shiftKey;this.metaKey=a.metaKey;this.state=a.state;this.j=a;a.defaultPrevented&&this.preventDefault();delete this.i}};u(T,S);h=T.prototype;h.target=null;h.relatedTarget=null;h.offsetX=0;h.offsetY=0;h.clientX=0;h.clientY=0;h.screenX=0;h.screenY=0;h.button=0;h.keyCode=0;
+h.charCode=0;h.ctrlKey=!1;h.altKey=!1;h.shiftKey=!1;h.metaKey=!1;h.j=null;h.preventDefault=function(){T.o.preventDefault.call(this);var a=this.j;if(a.preventDefault)a.preventDefault();else if(a.returnValue=!1,ua)try{if(a.ctrlKey||112<=a.keyCode&&123>=a.keyCode)a.keyCode=-1}catch(b){}};var va="closure_listenable_"+(1E6*Math.random()|0),wa=function(a){try{return!(!a||!a[va])}catch(b){return!1}},xa=0;var ya=function(a,b,c,d,e){this.c=a;this.e=null;this.src=b;this.type=c;this.capture=!!d;this.f=e;this.key=++xa;this.d=this.g=!1},za=function(a){a.d=!0;a.c=null;a.e=null;a.src=null;a.f=null};var U=function(a){this.src=a;this.b={};this.h=0};U.prototype.add=function(a,b,c,d,e){var f=this.b[a];f||(f=this.b[a]=[],this.h++);var g;n:{for(g=0;g<f.length;++g){var k=f[g];if(!k.d&&k.c==b&&k.capture==!!d&&k.f==e)break n}g=-1}-1<g?(a=f[g],c||(a.g=!1)):(a=new ya(b,this.src,a,!!d,e),a.g=c,f.push(a));return a};var Aa=function(a,b){var c=b.type;if(c in a.b){var d=a.b[c],e=B(d,b),f;if(f=0<=e)z(null!=d.length),A.splice.call(d,e,1);f&&(za(b),0==a.b[c].length&&(delete a.b[c],a.h--))}};var V="closure_lm_"+(1E6*Math.random()|0),W={},Ba=0,Da=function(){var a=Ca,b=ta?function(c){return a.call(b.src,b.c,c)}:function(c){c=a.call(b.src,b.c,c);if(!c)return c};return b},Ea=function(a,b,c,d,e){if("array"==n(b))for(var f=0;f<b.length;f++)Ea(a,b[f],c,d,e);else if(c=Fa(c),wa(a))a.k.add(b,c,!0,d,e);else{if(!b)throw Error("Invalid event type");var f=!!d,g=X(a);g||(a[V]=g=new U(a));c=g.add(b,c,!0,d,e);c.e||(d=Da(),c.e=d,d.src=a,d.c=c,a.addEventListener?a.addEventListener(b,d,f):a.attachEvent(b in
+W?W[b]:W[b]="on"+b,d),Ba++)}},Ha=function(a,b,c,d){var e=1;if(a=X(a))if(b=a.b[b])for(b=C(b),a=0;a<b.length;a++){var f=b[a];f&&f.capture==c&&!f.d&&(e&=!1!==Ga(f,d))}return Boolean(e)},Ga=function(a,b){var c=a.c,d=a.f||a.src;if(a.g&&"number"!=typeof a&&a&&!a.d){var e=a.src;if(wa(e))Aa(e.k,a);else{var f=a.type,g=a.e;e.removeEventListener?e.removeEventListener(f,g,a.capture):e.detachEvent&&e.detachEvent(f in W?W[f]:W[f]="on"+f,g);Ba--;(f=X(e))?(Aa(f,a),0==f.h&&(f.src=null,e[V]=null)):za(a)}}return c.call(d,
+b)},Ca=function(a,b){if(a.d)return!0;if(!ta){var c;if(!(c=b))n:{c=["window","event"];for(var d=m,e;e=c.shift();)if(null!=d[e])d=d[e];else{c=null;break n}c=d}e=c;c=new T(e,this);d=!0;if(!(0>e.keyCode||void 0!=e.returnValue)){n:{var f=!1;if(0==e.keyCode)try{e.keyCode=-1;break n}catch(g){f=!0}if(f||void 0==e.returnValue)e.returnValue=!0}e=[];for(f=c.currentTarget;f;f=f.parentNode)e.push(f);for(var f=a.type,k=e.length-1;!c.i&&0<=k;k--)c.currentTarget=e[k],d&=Ha(e[k],f,!0,c);for(k=0;!c.i&&k<e.length;k++)c.currentTarget=
+e[k],d&=Ha(e[k],f,!1,c)}return d}return Ga(a,new T(b,this))},X=function(a){a=a[V];return a instanceof U?a:null},Ia="__closure_events_fn_"+(1E9*Math.random()>>>0),Fa=function(a){z(a,"Listener can not be null.");if("function"==n(a))return a;z(a.handleEvent,"An object listener must have handleEvent method.");return a[Ia]||(a[Ia]=function(b){return a.handleEvent(b)})};var Z=function(a,b,c){"number"==typeof a?(this.a=new Date(a,b||0,c||1),Y(this,c||1)):(b=typeof a,"object"==b&&null!=a||"function"==b?(this.a=new Date(a.getFullYear(),a.getMonth(),a.getDate()),Y(this,a.getDate())):(this.a=new Date(aa()),this.a.setHours(0),this.a.setMinutes(0),this.a.setSeconds(0),this.a.setMilliseconds(0)))};h=Z.prototype;h.getFullYear=function(){return this.a.getFullYear()};h.getYear=function(){return this.getFullYear()};h.getMonth=function(){return this.a.getMonth()};h.getDate=function(){return this.a.getDate()};
+h.getTime=function(){return this.a.getTime()};h.getUTCHours=function(){return this.a.getUTCHours()};h.setFullYear=function(a){this.a.setFullYear(a)};h.setMonth=function(a){this.a.setMonth(a)};h.setDate=function(a){this.a.setDate(a)};
+h.add=function(a){if(a.n||a.m){var b=this.getMonth()+a.m+12*a.n,c=this.getYear()+Math.floor(b/12),b=b%12;0>b&&(b+=12);var d;n:{switch(b){case 1:d=0!=c%4||0==c%100&&0!=c%400?28:29;break n;case 5:case 8:case 10:case 3:d=30;break n}d=31}d=Math.min(d,this.getDate());this.setDate(1);this.setFullYear(c);this.setMonth(b);this.setDate(d)}a.l&&(b=new Date(this.getYear(),this.getMonth(),this.getDate(),12),a=new Date(b.getTime()+864E5*a.l),this.setDate(1),this.setFullYear(a.getFullYear()),this.setMonth(a.getMonth()),
+this.setDate(a.getDate()),Y(this,a.getDate()))};h.toString=function(){return[this.getFullYear(),x(this.getMonth()+1),x(this.getDate())].join("")+""};var Y=function(a,b){if(a.getDate()!=b){var c=a.getDate()<b?1:-1;a.a.setUTCHours(a.a.getUTCHours()+c)}};Z.prototype.valueOf=function(){return this.a.valueOf()};var $=function(){};$.p=function(){$.q||($.q=new $)};$.p();new Z(0,0,1);new Z(9999,11,31);I||K&&P("525");t("ae.init",function(){Ja();Ka();Ea(window,"load",function(){});La()});
+var Ja=function(){var a;a=document;if(a=q("ae-content")?a.getElementById("ae-content"):"ae-content"){a=Q("table","ae-table-striped",a);for(var b=0,c;c=a[b];b++){c=Q("tbody",null,c);for(var d=0,e;e=c[d];d++){e=Q("tr",null,e);for(var f=0,g;g=e[f];f++)f%2&&qa(g,"ae-even")}}}},Ka=function(){var a=Q(null,"ae-noscript",void 0);ca(C(a),function(a){sa(a,"ae-noscript")})},La=function(){m._gaq=m._gaq||[];m._gaq.push(function(){m._gaq._createAsyncTracker("UA-3739047-3","ae")._trackPageview()});(function(){var a=
+document.createElement("script");a.src=("https:"==document.location.protocol?"https://ssl":"http://www")+".google-analytics.com/ga.js";a.setAttribute("async","true");document.documentElement.firstChild.appendChild(a)})()};t("ae.trackPageView",function(){m._gaq&&m._gaq._getAsyncTracker("ae")._trackPageview()});var Na=function(a){if(void 0==a||null==a||0==a.length)return 0;a=Math.max.apply(Math,a);return Ma(a)},Ma=function(a){var b=5;2>b&&(b=2);b-=1;return Math.ceil(a/b)*b},Oa=function(a,b,c){a=a.getSelection();1==a.length&&(a=a[0],null!=a.row&&(null!=b.starttime&&(c+="&starttime="+b.starttime),null!=b.endtime&&(c+="&endtime="+b.endtime),null!=b.latency_lower&&(c+="&latency_lower="+b.latency_lower),null!=b.latency_upper&&(c+="&latency_upper="+b.latency_upper),b=c+"&detail="+a.row,window.location.href=b))},
+Pa=function(a,b,c,d,e){var f=new google.visualization.DataTable;f.addColumn("string","");f.addColumn("number","");f.addColumn({type:"string",role:"tooltip"});for(var g=0;g<b.length;g++)f.addRow(["",b[g],c[g]]);c=Math.max(10*b.length,200);b=Na(b);a=new google.visualization.ColumnChart(document.getElementById("rpctime-"+a));a.draw(f,{height:100,width:c,legend:"none",chartArea:{left:40},fontSize:11,vAxis:{minValue:0,maxValue:b,gridlines:{count:5}}});google.visualization.events.addListener(a,"select",
+r(Oa,a,d,e))};t("ae.Charts.latencyHistogram",function(a,b,c){var d=new google.visualization.DataTable;d.addColumn("string","");d.addColumn("number","");for(var e=0;e<b.length;e++)d.addRow([""+a[e],b[e]]);for(e=b.length;e<a.length;e++)d.addRow([""+a[e],0]);b=Na(b);(new google.visualization.ColumnChart(document.getElementById("latency-"+c))).draw(d,{legend:"none",width:20*a.length,height:200,vAxis:{maxValue:b,gridlines:{count:5}}})});
+t("ae.Charts.latencyTimestampScatter",function(a,b,c,d,e){var f=new google.visualization.DataTable;f.addColumn("number","Time (seconds from start)");f.addColumn("number","Latency");for(var g=0;g<a.length;g++){var k=Math.round(a[g]-c);f.addRow([k,b[g]])}a=d.starttime?d.starttime:0;b=new google.visualization.ScatterChart(document.getElementById("LatencyVsTimestamp"));b.draw(f,{hAxis:{title:"Time (seconds from start of recording)",minValue:a},vAxis:{title:"Request Latency (milliseconds)",minValue:0},
+tooltip:{trigger:"none"},legend:"none"});google.visualization.events.addListener(b,"select",r(Oa,b,d,e))});
+t("ae.Charts.entityCountBarChart",function(a,b,c,d){var e=new google.visualization.DataTable;e.addColumn("string","");e.addColumn("number","Reads");e.addColumn({type:"string",role:"tooltip"});e.addColumn("number","Misses");e.addColumn({type:"string",role:"tooltip"});e.addColumn("number","Writes");e.addColumn({type:"string",role:"tooltip"});var f=50;f>b.length&&(f=b.length);for(var g=0;g<f;g++)e.addRow(["",b[g][1]-b[g][3],b[g][0],b[g][3],b[g][0],b[g][2],b[g][0]]);b=20*f;f=b+130;a=new google.visualization.ColumnChart(document.getElementById(d+
+"-"+a));c=Ma(c);a.draw(e,{height:100,width:f,chartArea:{width:b},fontSize:10,isStacked:!0,vAxis:{minValue:0,maxValue:c,gridlines:{count:5}}})});
+t("ae.Charts.rpcVariationCandlestick",function(a){var b=new google.visualization.DataTable;b.addColumn("string","");b.addColumn("number","");b.addColumn("number","");b.addColumn("number","");b.addColumn("number","");b.addRows(a);(new google.visualization.CandlestickChart(document.getElementById("rpcvariation"))).draw(b,{vAxis:{title:"RPC Latency variation (milliseconds)"},hAxis:{textPosition:"out",slantedText:!0,slantedTextAngle:45,textStyle:{fontSize:13}},height:250,chartArea:{top:10,height:100},
+legend:"none",tooltip:{trigger:"none"}})});t("ae.Charts.totalTimeBarChart",function(a,b,c,d){for(var e=[],f=0;f<b.length;f++)e[f]=b[f]+" milliseconds";Pa(a,b,e,c,d)});t("ae.Charts.rpcTimeBarChart",function(a,b,c,d,e){var f=[],g=[],k=c.indices,w=c.times;c=c.stats;for(var p=0;p<b;p++)f[p]=0,g[p]=null;for(p=0;p<k.length;p++){f[k[p]]=w[p];b=c[p];var l="Calls: "+b[0];if(0<b[1]||0<b[2]||0<b[3])l+=" Entities";0<b[1]&&(l+=" R:"+b[1]);0<b[2]&&(l+=" W:"+b[2]);0<b[3]&&(l+=" M:"+b[3]);g[k[p]]=l}Pa(a,f,g,d,e)});})();
diff --git a/google/appengine/ext/appstats/static/appstats_js.js b/google/appengine/ext/appstats/static/appstats_js.js
index 7d18518..5958a1e 100644
--- a/google/appengine/ext/appstats/static/appstats_js.js
+++ b/google/appengine/ext/appstats/static/appstats_js.js
@@ -1,82 +1,82 @@
 /* Copyright 2008-10 Google Inc. All Rights Reserved. */ (function(){var f,l=this,aa=function(){},ba=function(a){a.aa=function(){return a.Eb?a.Eb:a.Eb=new a}},ca=function(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=
-typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";else if("function"==b&&"undefined"==typeof a.call)return"object";return b},da=function(a){return"array"==ca(a)},ea=function(a){var b=ca(a);return"array"==b||"object"==b&&"number"==typeof a.length},m=function(a){return"string"==typeof a},n=function(a){return"function"==ca(a)},fa=function(a){var b=typeof a;return"object"==b&&null!=a||"function"==b},p=function(a){return a[ga]||
-(a[ga]=++ha)},ga="closure_uid_"+(1E9*Math.random()>>>0),ha=0,ia=function(a,b){var c=Array.prototype.slice.call(arguments,1);return function(){var b=c.slice();b.push.apply(b,arguments);return a.apply(this,b)}},ja=function(a,b){var c=a.split("."),d=l;c[0]in d||!d.execScript||d.execScript("var "+c[0]);for(var e;c.length&&(e=c.shift());)c.length||void 0===b?d=d[e]?d[e]:d[e]={}:d[e]=b},r=function(a,b){function c(){}c.prototype=b.prototype;a.f=b.prototype;a.prototype=new c;a.prototype.constructor=a};var ka=function(a){Error.captureStackTrace?Error.captureStackTrace(this,ka):this.stack=Error().stack||"";a&&(this.message=String(a))};r(ka,Error);ka.prototype.name="CustomError";var la=function(a,b){for(var c=a.split("%s"),d="",e=Array.prototype.slice.call(arguments,1);e.length&&1<c.length;)d+=c.shift()+e.shift();return d+c.join("%s")},na=function(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")},ta=function(a){if(!oa.test(a))return a;-1!=a.indexOf("&")&&(a=a.replace(pa,"&amp;"));-1!=a.indexOf("<")&&(a=a.replace(qa,"&lt;"));-1!=a.indexOf(">")&&(a=a.replace(ra,"&gt;"));-1!=a.indexOf('"')&&(a=a.replace(sa,"&quot;"));return a},pa=/&/g,qa=/</g,ra=/>/g,sa=/\"/g,oa=/[&<>\"]/;var ua=function(a,b){b.unshift(a);ka.call(this,la.apply(null,b));b.shift()};r(ua,ka);ua.prototype.name="AssertionError";var va=function(a,b,c){var d="Assertion failed";if(b)var d=d+(": "+b),e=c;else a&&(d+=": "+a,e=null);throw new ua(""+d,e||[]);},s=function(a,b,c){a||va("",b,Array.prototype.slice.call(arguments,2))},wa=function(a,b,c,d){a instanceof b||va("instanceof check failed.",c,Array.prototype.slice.call(arguments,3))};var t=Array.prototype,xa=t.indexOf?function(a,b,c){s(null!=a.length);return t.indexOf.call(a,b,c)}:function(a,b,c){c=null==c?0:0>c?Math.max(0,a.length+c):c;if(m(a))return m(b)&&1==b.length?a.indexOf(b,c):-1;for(;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},ya=t.forEach?function(a,b,c){s(null!=a.length);t.forEach.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=m(a)?a.split(""):a,g=0;g<d;g++)g in e&&b.call(c,e[g],g,a)},za=t.filter?function(a,b,c){s(null!=a.length);return t.filter.call(a,
-b,c)}:function(a,b,c){for(var d=a.length,e=[],g=0,h=m(a)?a.split(""):a,k=0;k<d;k++)if(k in h){var q=h[k];b.call(c,q,k,a)&&(e[g++]=q)}return e},Aa=t.every?function(a,b,c){s(null!=a.length);return t.every.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=m(a)?a.split(""):a,g=0;g<d;g++)if(g in e&&!b.call(c,e[g],g,a))return!1;return!0},u=function(a,b){return 0<=xa(a,b)},Ba=function(a,b){var c=xa(a,b),d;if(d=0<=c)s(null!=a.length),t.splice.call(a,c,1);return d},Ca=function(a){var b=a.length;if(0<b){for(var c=
-Array(b),d=0;d<b;d++)c[d]=a[d];return c}return[]},Ea=function(a,b,c,d){s(null!=a.length);t.splice.apply(a,Da(arguments,1))},Da=function(a,b,c){s(null!=a.length);return 2>=arguments.length?t.slice.call(a,b):t.slice.call(a,b,c)};var Fa=function(a,b){for(var c in a)b.call(void 0,a[c],c,a)},Ga=function(a,b){for(var c in a)if(a[c]==b)return!0;return!1},Ha=function(a,b,c){if(b in a)throw Error('The object already contains the key "'+b+'"');a[b]=c},Ia=function(a){var b={},c;for(c in a)b[a[c]]=c;return b},Ja="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),Ka=function(a,b){for(var c,d,e=1;e<arguments.length;e++){d=arguments[e];for(c in d)a[c]=d[c];for(var g=0;g<Ja.length;g++)c=
-Ja[g],Object.prototype.hasOwnProperty.call(d,c)&&(a[c]=d[c])}};var v,La,Ma,Na,Oa=function(){return l.navigator?l.navigator.userAgent:null};Na=Ma=La=v=!1;var Pa;if(Pa=Oa()){var Qa=l.navigator;v=0==Pa.lastIndexOf("Opera",0);La=!v&&(-1!=Pa.indexOf("MSIE")||-1!=Pa.indexOf("Trident"));Ma=!v&&-1!=Pa.indexOf("WebKit");Na=!v&&!Ma&&!La&&"Gecko"==Qa.product}var Ra=v,w=La,x=Na,y=Ma,Sa=l.navigator,A=-1!=(Sa&&Sa.platform||"").indexOf("Mac"),Ta=function(){var a=l.document;return a?a.documentMode:void 0},Ua;
-t:{var Va="",Wa;if(Ra&&l.opera)var Xa=l.opera.version,Va="function"==typeof Xa?Xa():Xa;else if(x?Wa=/rv\:([^\);]+)(\)|;)/:w?Wa=/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/:y&&(Wa=/WebKit\/(\S+)/),Wa)var Ya=Wa.exec(Oa()),Va=Ya?Ya[1]:"";if(w){var Za=Ta();if(Za>parseFloat(Va)){Ua=String(Za);break t}}Ua=Va}
-var $a=Ua,ab={},B=function(a){var b;if(!(b=ab[a])){b=0;for(var c=na(String($a)).split("."),d=na(String(a)).split("."),e=Math.max(c.length,d.length),g=0;0==b&&g<e;g++){var h=c[g]||"",k=d[g]||"",q=RegExp("(\\d*)(\\D*)","g"),ma=RegExp("(\\d*)(\\D*)","g");do{var z=q.exec(h)||["","",""],I=ma.exec(k)||["","",""];if(0==z[0].length&&0==I[0].length)break;b=((0==z[1].length?0:parseInt(z[1],10))<(0==I[1].length?0:parseInt(I[1],10))?-1:(0==z[1].length?0:parseInt(z[1],10))>(0==I[1].length?0:parseInt(I[1],10))?
-1:0)||((0==z[2].length)<(0==I[2].length)?-1:(0==z[2].length)>(0==I[2].length)?1:0)||(z[2]<I[2]?-1:z[2]>I[2]?1:0)}while(0==b)}b=ab[a]=0<=b}return b},bb=l.document,cb=bb&&w?Ta()||("CSS1Compat"==bb.compatMode?parseInt($a,10):5):void 0;var db,eb=!w||w&&9<=cb;!x&&!w||w&&w&&9<=cb||x&&B("1.9.1");var fb=w&&!B("9");var C=function(a){a=a.className;return m(a)&&a.match(/\S+/g)||[]},D=function(a,b){for(var c=C(a),d=Da(arguments,1),e=c.length+d.length,g=c,h=0;h<d.length;h++)u(g,d[h])||g.push(d[h]);a.className=c.join(" ");return c.length==e},E=function(a,b){var c=C(a),d=Da(arguments,1),e=gb(c,d);a.className=e.join(" ");return e.length==c.length-d.length},gb=function(a,b){return za(a,function(a){return!u(b,a)})};var jb=function(a){return a?new hb(ib(a)):db||(db=new hb)},kb=function(a,b){return m(b)?a.getElementById(b):b},lb=function(a,b,c){var d=document;c=c||d;a=a&&"*"!=a?a.toUpperCase():"";if(c.querySelectorAll&&c.querySelector&&(a||b))return c.querySelectorAll(a+(b?"."+b:""));if(b&&c.getElementsByClassName){c=c.getElementsByClassName(b);if(a){for(var d={},e=0,g=0,h;h=c[g];g++)a==h.nodeName&&(d[e++]=h);d.length=e;return d}return c}c=c.getElementsByTagName(a||"*");if(b){d={};for(g=e=0;h=c[g];g++)a=h.className,
-"function"==typeof a.split&&u(a.split(/\s+/),b)&&(d[e++]=h);d.length=e;return d}return c},nb=function(a,b){Fa(b,function(b,d){"style"==d?a.style.cssText=b:"class"==d?a.className=b:"for"==d?a.htmlFor=b:d in mb?a.setAttribute(mb[d],b):0==d.lastIndexOf("aria-",0)||0==d.lastIndexOf("data-",0)?a.setAttribute(d,b):a[d]=b})},mb={cellpadding:"cellPadding",cellspacing:"cellSpacing",colspan:"colSpan",frameborder:"frameBorder",height:"height",maxlength:"maxLength",role:"role",rowspan:"rowSpan",type:"type",usemap:"useMap",
-valign:"vAlign",width:"width"},pb=function(a,b,c){return ob(document,arguments)},ob=function(a,b){var c=b[0],d=b[1];if(!eb&&d&&(d.name||d.type)){c=["<",c];d.name&&c.push(' name="',ta(d.name),'"');if(d.type){c.push(' type="',ta(d.type),'"');var e={};Ka(e,d);delete e.type;d=e}c.push(">");c=c.join("")}c=a.createElement(c);d&&(m(d)?c.className=d:da(d)?D.apply(null,[c].concat(d)):nb(c,d));2<b.length&&qb(a,c,b);return c},qb=function(a,b,c){function d(c){c&&b.appendChild(m(c)?a.createTextNode(c):c)}for(var e=
-2;e<c.length;e++){var g=c[e];if(!ea(g)||fa(g)&&0<g.nodeType)d(g);else{var h;t:{if(g&&"number"==typeof g.length){if(fa(g)){h="function"==typeof g.item||"string"==typeof g.item;break t}if(n(g)){h="function"==typeof g.item;break t}}h=!1}ya(h?Ca(g):g,d)}}},rb=function(a,b){if(a.contains&&1==b.nodeType)return a==b||a.contains(b);if("undefined"!=typeof a.compareDocumentPosition)return a==b||Boolean(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a},ib=function(a){return 9==a.nodeType?
-a:a.ownerDocument||a.document},sb=function(a,b){if("textContent"in a)a.textContent=b;else if(a.firstChild&&3==a.firstChild.nodeType){for(;a.lastChild!=a.firstChild;)a.removeChild(a.lastChild);a.firstChild.data=b}else{for(var c;c=a.firstChild;)a.removeChild(c);a.appendChild(ib(a).createTextNode(String(b)))}},tb={SCRIPT:1,STYLE:1,HEAD:1,IFRAME:1,OBJECT:1},ub={IMG:" ",BR:"\n"},vb=function(a){var b=a.getAttributeNode("tabindex");return b&&b.specified?(a=a.tabIndex,"number"==typeof a&&0<=a&&32768>a):!1},
-wb=function(a,b,c){if(!(a.nodeName in tb))if(3==a.nodeType)c?b.push(String(a.nodeValue).replace(/(\r\n|\r|\n)/g,"")):b.push(a.nodeValue);else if(a.nodeName in ub)b.push(ub[a.nodeName]);else for(a=a.firstChild;a;)wb(a,b,c),a=a.nextSibling},hb=function(a){this.F=a||l.document||document};f=hb.prototype;f.kb=jb;f.a=function(a){return kb(this.F,a)};f.r=function(a,b,c){return ob(this.F,arguments)};f.createElement=function(a){return this.F.createElement(a)};f.createTextNode=function(a){return this.F.createTextNode(String(a))};
-f.appendChild=function(a,b){a.appendChild(b)};f.contains=rb;var xb=function(a){xb[" "](a);return a};xb[" "]=aa;var yb=!w||w&&9<=cb,zb=!w||w&&9<=cb,Ab=w&&!B("9");!y||B("528");x&&B("1.9b")||w&&B("8")||Ra&&B("9.5")||y&&B("528");x&&!B("8")||w&&B("9");var Bb=function(){};Bb.prototype.Tb=!1;var F=function(a,b){this.type=a;this.currentTarget=this.target=b};f=F.prototype;f.R=!1;f.defaultPrevented=!1;f.xb=!0;f.stopPropagation=function(){this.R=!0};f.preventDefault=function(){this.defaultPrevented=!0;this.xb=!1};var G=function(a,b){a&&Cb(this,a,b)};r(G,F);var Db=[1,4,2];f=G.prototype;f.target=null;f.relatedTarget=null;f.offsetX=0;f.offsetY=0;f.clientX=0;f.clientY=0;f.screenX=0;f.screenY=0;f.button=0;f.keyCode=0;f.charCode=0;f.ctrlKey=!1;f.altKey=!1;f.shiftKey=!1;f.metaKey=!1;f.bb=!1;f.P=null;
-var Cb=function(a,b,c){var d=a.type=b.type;F.call(a,d);a.target=b.target||b.srcElement;a.currentTarget=c;if(c=b.relatedTarget){if(x){var e;t:{try{xb(c.nodeName);e=!0;break t}catch(g){}e=!1}e||(c=null)}}else"mouseover"==d?c=b.fromElement:"mouseout"==d&&(c=b.toElement);a.relatedTarget=c;a.offsetX=y||void 0!==b.offsetX?b.offsetX:b.layerX;a.offsetY=y||void 0!==b.offsetY?b.offsetY:b.layerY;a.clientX=void 0!==b.clientX?b.clientX:b.pageX;a.clientY=void 0!==b.clientY?b.clientY:b.pageY;a.screenX=b.screenX||
-0;a.screenY=b.screenY||0;a.button=b.button;a.keyCode=b.keyCode||0;a.charCode=b.charCode||("keypress"==d?b.keyCode:0);a.ctrlKey=b.ctrlKey;a.altKey=b.altKey;a.shiftKey=b.shiftKey;a.metaKey=b.metaKey;a.bb=A?b.metaKey:b.ctrlKey;a.state=b.state;a.P=b;b.defaultPrevented&&a.preventDefault();delete a.R},Eb=function(a){return yb?0==a.P.button:"click"==a.type?!0:!!(a.P.button&Db[0])};
-G.prototype.stopPropagation=function(){G.f.stopPropagation.call(this);this.P.stopPropagation?this.P.stopPropagation():this.P.cancelBubble=!0};G.prototype.preventDefault=function(){G.f.preventDefault.call(this);var a=this.P;if(a.preventDefault)a.preventDefault();else if(a.returnValue=!1,Ab)try{if(a.ctrlKey||112<=a.keyCode&&123>=a.keyCode)a.keyCode=-1}catch(b){}};var Fb="closure_listenable_"+(1E6*Math.random()|0),Gb=0;var Hb=function(a,b,c,d,e){this.$=a;this.Da=null;this.src=b;this.type=c;this.capture=!!d;this.Ia=e;this.key=++Gb;this.ha=this.Ga=!1},Ib=function(a){a.ha=!0;a.$=null;a.Da=null;a.src=null;a.Ia=null};var Jb=function(a){this.src=a;this.p={};this.va=0};Jb.prototype.add=function(a,b,c,d,e){var g=this.p[a];g||(g=this.p[a]=[],this.va++);var h=Kb(g,b,d,e);-1<h?(a=g[h],c||(a.Ga=!1)):(a=new Hb(b,this.src,a,!!d,e),a.Ga=c,g.push(a));return a};Jb.prototype.remove=function(a,b,c,d){if(!(a in this.p))return!1;var e=this.p[a];b=Kb(e,b,c,d);return-1<b?(Ib(e[b]),s(null!=e.length),t.splice.call(e,b,1),0==e.length&&(delete this.p[a],this.va--),!0):!1};
-var Lb=function(a,b){var c=b.type;if(!(c in a.p))return!1;var d=Ba(a.p[c],b);d&&(Ib(b),0==a.p[c].length&&(delete a.p[c],a.va--));return d};Jb.prototype.$a=function(a){var b=0,c;for(c in this.p)if(!a||c==a){for(var d=this.p[c],e=0;e<d.length;e++)++b,Ib(d[e]);delete this.p[c];this.va--}return b};Jb.prototype.wa=function(a,b,c,d){a=this.p[a];var e=-1;a&&(e=Kb(a,b,c,d));return-1<e?a[e]:null};var Kb=function(a,b,c,d){for(var e=0;e<a.length;++e){var g=a[e];if(!g.ha&&g.$==b&&g.capture==!!c&&g.Ia==d)return e}return-1};var Mb={},Nb={},Ob={},H=function(a,b,c,d,e){if(da(b)){for(var g=0;g<b.length;g++)H(a,b[g],c,d,e);return null}c=Pb(c);if(a&&a[Fb])a=a.d(b,c,d,e);else{g=c;if(!b)throw Error("Invalid event type");c=!!d;var h=p(a),k=Nb[h];k||(Nb[h]=k=new Jb(a));d=k.add(b,g,!1,d,e);d.Da||(e=Qb(),d.Da=e,e.src=a,e.$=d,a.addEventListener?a.addEventListener(b,e,c):a.attachEvent(b in Ob?Ob[b]:Ob[b]="on"+b,e),Mb[d.key]=d);a=d}return a},Qb=function(){var a=Rb,b=zb?function(c){return a.call(b.src,b.$,c)}:function(c){c=a.call(b.src,
-b.$,c);if(!c)return c};return b},Sb=function(a,b,c,d,e){if(da(b))for(var g=0;g<b.length;g++)Sb(a,b[g],c,d,e);else c=Pb(c),a&&a[Fb]?a.w(b,c,d,e):a&&(d=!!d,(a=Tb(a))&&(b=a.wa(b,c,d,e))&&J(b))},J=function(a){if("number"==typeof a||!a||a.ha)return!1;var b=a.src;if(b&&b[Fb])return Lb(b.ba,a);var c=a.type,d=a.Da;b.removeEventListener?b.removeEventListener(c,d,a.capture):b.detachEvent&&b.detachEvent(c in Ob?Ob[c]:Ob[c]="on"+c,d);(c=Tb(b))?(Lb(c,a),0==c.va&&(c.src=null,delete Nb[p(b)])):Ib(a);delete Mb[a.key];
-return!0},Vb=function(a,b,c,d){var e=1;if(a=Tb(a))if(b=a.p[b])for(b=Ca(b),a=0;a<b.length;a++){var g=b[a];g&&(g.capture==c&&!g.ha)&&(e&=!1!==Ub(g,d))}return Boolean(e)},Ub=function(a,b){var c=a.$,d=a.Ia||a.src;a.Ga&&J(a);return c.call(d,b)},Rb=function(a,b){if(a.ha)return!0;if(!zb){var c;if(!(c=b))t:{c=["window","event"];for(var d=l,e;e=c.shift();)if(null!=d[e])d=d[e];else{c=null;break t}c=d}e=c;c=new G(e,this);d=!0;if(!(0>e.keyCode||void 0!=e.returnValue)){t:{var g=!1;if(0==e.keyCode)try{e.keyCode=
--1;break t}catch(h){g=!0}if(g||void 0==e.returnValue)e.returnValue=!0}e=[];for(g=c.currentTarget;g;g=g.parentNode)e.push(g);for(var g=a.type,k=e.length-1;!c.R&&0<=k;k--)c.currentTarget=e[k],d&=Vb(e[k],g,!0,c);for(k=0;!c.R&&k<e.length;k++)c.currentTarget=e[k],d&=Vb(e[k],g,!1,c)}return d}return Ub(a,new G(b,this))},Tb=function(a){return a[ga]?Nb[p(a)]||null:null},Wb="__closure_events_fn_"+(1E9*Math.random()>>>0),Pb=function(a){s(a,"Listener can not be null.");if(n(a))return a;s(a.handleEvent,"An object listener must have handleEvent method.");
-return a[Wb]||(a[Wb]=function(b){return a.handleEvent(b)})};var K=function(a){this.Db=a;this.La={}};r(K,Bb);var Xb=[];K.prototype.d=function(a,b,c,d,e){da(b)||(Xb[0]=b,b=Xb);for(var g=0;g<b.length;g++){var h=H(a,b[g],c||this,d||!1,e||this.Db||this);if(!h)break;this.La[h.key]=h}return this};K.prototype.w=function(a,b,c,d,e){if(da(b))for(var g=0;g<b.length;g++)this.w(a,b[g],c,d,e);else e=e||this.Db||this,c=Pb(c||this),d=!!d,b=a&&a[Fb]?a.wa(b,c,d,e):a?(a=Tb(a))?a.wa(b,c,d,e):null:null,b&&(J(b),delete this.La[b.key]);return this};
-K.prototype.$a=function(){Fa(this.La,J);this.La={}};K.prototype.handleEvent=function(){throw Error("EventHandler.handleEvent not implemented");};var L=function(){this.ba=new Jb(this);this.cc=this};r(L,Bb);L.prototype[Fb]=!0;f=L.prototype;f.mb=null;f.Ya=function(a){this.mb=a};f.addEventListener=function(a,b,c,d){H(this,a,b,c,d)};f.removeEventListener=function(a,b,c,d){Sb(this,a,b,c,d)};
-f.dispatchEvent=function(a){Yb(this);var b,c=this.mb;if(c){b=[];for(var d=1;c;c=c.mb)b.push(c),s(1E3>++d,"infinite loop")}c=this.cc;d=a.type||a;if(m(a))a=new F(a,c);else if(a instanceof F)a.target=a.target||c;else{var e=a;a=new F(d,c);Ka(a,e)}var e=!0,g;if(b)for(var h=b.length-1;!a.R&&0<=h;h--)g=a.currentTarget=b[h],e=Zb(g,d,!0,a)&&e;a.R||(g=a.currentTarget=c,e=Zb(g,d,!0,a)&&e,a.R||(e=Zb(g,d,!1,a)&&e));if(b)for(h=0;!a.R&&h<b.length;h++)g=a.currentTarget=b[h],e=Zb(g,d,!1,a)&&e;return e};
-f.d=function(a,b,c,d){Yb(this);return this.ba.add(a,b,!1,c,d)};f.w=function(a,b,c,d){return this.ba.remove(a,b,c,d)};var Zb=function(a,b,c,d){b=a.ba.p[b];if(!b)return!0;b=Ca(b);for(var e=!0,g=0;g<b.length;++g){var h=b[g];if(h&&!h.ha&&h.capture==c){var k=h.$,q=h.Ia||h.src;h.Ga&&Lb(a.ba,h);e=!1!==k.call(q,d)&&e}}return e&&!1!=d.xb};L.prototype.wa=function(a,b,c,d){return this.ba.wa(a,b,c,d)};var Yb=function(a){s(a.ba,"Event target is not initialized. Did you call the superclass (goog.events.EventTarget) constructor?")};var M=function(a,b){a.style.display=b?"":"none"},$b=x?"MozUserSelect":y?"WebkitUserSelect":null,ac=function(a,b,c){c=c?null:a.getElementsByTagName("*");if($b){if(b=b?"none":"",a.style[$b]=b,c){a=0;for(var d;d=c[a];a++)d.style[$b]=b}}else if(w||Ra)if(b=b?"on":"",a.setAttribute("unselectable",b),c)for(a=0;d=c[a];a++)d.setAttribute("unselectable",b)};var bc=function(){};ba(bc);bc.prototype.fc=0;bc.aa();var N=function(a){L.call(this);this.n=a||jb();this.sa=cc};r(N,L);N.prototype.ec=bc.aa();var cc=null,dc=function(a,b){switch(a){case 1:return b?"disable":"enable";case 2:return b?"highlight":"unhighlight";case 4:return b?"activate":"deactivate";case 8:return b?"select":"unselect";case 16:return b?"check":"uncheck";case 32:return b?"focus":"blur";case 64:return b?"open":"close"}throw Error("Invalid component state");};f=N.prototype;f.O=null;f.e=!1;f.c=null;f.sa=null;f.o=null;f.s=null;f.i=null;
-f.Qb=!1;var ec=function(a){return a.O||(a.O=":"+(a.ec.fc++).toString(36))},fc=function(a,b){if(a.o&&a.o.i){var c=a.o.i,d=a.O;d in c&&delete c[d];Ha(a.o.i,b,a)}a.O=b};N.prototype.a=function(){return this.c};var gc=function(a){return a.ib||(a.ib=new K(a))},hc=function(a,b){if(a==b)throw Error("Unable to set parent component");if(b&&a.o&&a.O&&a.o.i&&a.O&&(a.O in a.o.i&&a.o.i[a.O])&&a.o!=b)throw Error("Unable to set parent component");a.o=b;N.f.Ya.call(a,b)};f=N.prototype;f.getParent=function(){return this.o};
-f.Ya=function(a){if(this.o&&this.o!=a)throw Error("Method not supported");N.f.Ya.call(this,a)};f.kb=function(){return this.n};f.r=function(){this.c=this.n.createElement("div")};f.K=function(a){if(this.e)throw Error("Component already rendered");if(a&&this.Z(a)){this.Qb=!0;var b=ib(a);this.n&&this.n.F==b||(this.n=jb(a));this.Wa(a);this.G()}else throw Error("Invalid element to decorate");};f.Z=function(){return!0};f.Wa=function(a){this.c=a};
-f.G=function(){this.e=!0;ic(this,function(a){!a.e&&a.a()&&a.G()})};f.da=function(){ic(this,function(a){a.e&&a.da()});this.ib&&this.ib.$a();this.e=!1};f.Ca=function(a,b){this.Ta(a,jc(this),b)};
-f.Ta=function(a,b,c){if(a.e&&(c||!this.e))throw Error("Component already rendered");if(0>b||b>jc(this))throw Error("Child component index out of bounds");this.i&&this.s||(this.i={},this.s=[]);if(a.getParent()==this){var d=ec(a);this.i[d]=a;Ba(this.s,a)}else Ha(this.i,ec(a),a);hc(a,this);Ea(this.s,b,0,a);if(a.e&&this.e&&a.getParent()==this)c=this.C(),c.insertBefore(a.a(),c.childNodes[b]||null);else if(c){this.c||this.r();c=O(this,b+1);b=this.C();c=c?c.c:null;if(a.e)throw Error("Component already rendered");
-a.c||a.r();b?b.insertBefore(a.c,c||null):a.n.F.body.appendChild(a.c);a.o&&!a.o.e||a.G()}else this.e&&(!a.e&&a.c&&a.c.parentNode&&1==a.c.parentNode.nodeType)&&a.G()};f.C=function(){return this.c};
-var kc=function(a){if(null==a.sa){var b;t:{b=a.e?a.c:a.n.F.body;var c=ib(b);if(c.defaultView&&c.defaultView.getComputedStyle&&(b=c.defaultView.getComputedStyle(b,null))){b=b.direction||b.getPropertyValue("direction")||"";break t}b=""}a.sa="rtl"==(b||((a.e?a.c:a.n.F.body).currentStyle?(a.e?a.c:a.n.F.body).currentStyle.direction:null)||(a.e?a.c:a.n.F.body).style&&(a.e?a.c:a.n.F.body).style.direction)}return a.sa};
-N.prototype.qa=function(a){if(this.e)throw Error("Component already rendered");this.sa=a};var jc=function(a){return a.s?a.s.length:0},O=function(a,b){return a.s?a.s[b]||null:null},ic=function(a,b,c){a.s&&ya(a.s,b,c)},lc=function(a,b){return a.s&&b?xa(a.s,b):-1};
-N.prototype.removeChild=function(a,b){if(a){var c=m(a)?a:ec(a);a=this.i&&c?(c in this.i?this.i[c]:void 0)||null:null;if(c&&a){var d=this.i;c in d&&delete d[c];Ba(this.s,a);b&&(a.da(),a.c&&(c=a.c)&&c.parentNode&&c.parentNode.removeChild(c));hc(a,null)}}if(!a)throw Error("Child is not in parent component");return a};var mc,nc={lc:"activedescendant",qc:"atomic",rc:"autocomplete",tc:"busy",wc:"checked",Bc:"controls",Dc:"describedby",Gc:"disabled",Ic:"dropeffect",Jc:"expanded",Kc:"flowto",Mc:"grabbed",Qc:"haspopup",Sc:"hidden",Uc:"invalid",Vc:"label",Wc:"labelledby",Xc:"level",bd:"live",md:"multiline",nd:"multiselectable",rd:"orientation",sd:"owns",td:"posinset",vd:"pressed",zd:"readonly",Bd:"relevant",Cd:"required",Id:"selected",Kd:"setsize",Md:"sort",Zd:"valuemax",$d:"valuemin",ae:"valuenow",be:"valuetext"};var oc={mc:"alert",nc:"alertdialog",oc:"application",pc:"article",sc:"banner",uc:"button",vc:"checkbox",xc:"columnheader",yc:"combobox",zc:"complementary",Ac:"contentinfo",Cc:"definition",Ec:"dialog",Fc:"directory",Hc:"document",Lc:"form",Nc:"grid",Oc:"gridcell",Pc:"group",Rc:"heading",Tc:"img",Yc:"link",Zc:"list",$c:"listbox",ad:"listitem",cd:"log",dd:"main",ed:"marquee",fd:"math",gd:"menu",hd:"menubar",jd:"menuitem",kd:"menuitemcheckbox",ld:"menuitemradio",od:"navigation",pd:"note",qd:"option",
-ud:"presentation",wd:"progressbar",xd:"radio",yd:"radiogroup",Ad:"region",Dd:"row",Ed:"rowgroup",Fd:"rowheader",Gd:"scrollbar",Hd:"search",Jd:"separator",Ld:"slider",Nd:"spinbutton",Od:"status",Pd:"tab",Qd:"tablist",Rd:"tabpanel",Sd:"textbox",Td:"timer",Ud:"toolbar",Vd:"tooltip",Wd:"tree",Xd:"treegrid",Yd:"treeitem"};var pc=function(a,b){b?(s(Ga(oc,b),"No such ARIA role "+b),a.setAttribute("role",b)):a.removeAttribute("role")},rc=function(a,b,c){ea(c)&&(c=c.join(" "));var d=qc(b);""===c||void 0==c?(mc||(mc={atomic:!1,autocomplete:"none",dropeffect:"none",haspopup:!1,live:"off",multiline:!1,multiselectable:!1,orientation:"vertical",readonly:!1,relevant:"additions text",required:!1,sort:"none",busy:!1,disabled:!1,hidden:!1,invalid:"false"}),c=mc,b in c?a.setAttribute(d,c[b]):a.removeAttribute(d)):a.setAttribute(d,
-c)},qc=function(a){s(a,"ARIA attribute cannot be empty.");s(Ga(nc,a),"No such ARIA attribute "+a);return"aria-"+a};var tc=function(a,b,c,d,e){if(!(w||y&&B("525")))return!0;if(A&&e)return sc(a);if(e&&!d||!c&&(17==b||18==b||A&&91==b))return!1;if(y&&d&&c)switch(a){case 220:case 219:case 221:case 192:case 186:case 189:case 187:case 188:case 190:case 191:case 192:case 222:return!1}if(w&&d&&b==a)return!1;switch(a){case 13:return!(w&&w&&9<=cb);case 27:return!y}return sc(a)},sc=function(a){if(48<=a&&57>=a||96<=a&&106>=a||65<=a&&90>=a||y&&0==a)return!0;switch(a){case 32:case 63:case 107:case 109:case 110:case 111:case 186:case 59:case 189:case 187:case 61:case 188:case 190:case 191:case 192:case 222:case 219:case 220:case 221:return!0;
-default:return!1}},uc=function(a){switch(a){case 61:return 187;case 59:return 186;case 173:return 189;case 224:return 91;case 0:return 224;default:return a}};var P=function(a,b){L.call(this);a&&vc(this,a,b)};r(P,L);f=P.prototype;f.c=null;f.Ea=null;f.Xa=null;f.Fa=null;f.t=-1;f.N=-1;f.jb=!1;
-var wc={3:13,12:144,63232:38,63233:40,63234:37,63235:39,63236:112,63237:113,63238:114,63239:115,63240:116,63241:117,63242:118,63243:119,63244:120,63245:121,63246:122,63247:123,63248:44,63272:46,63273:36,63275:35,63276:33,63277:34,63289:144,63302:45},xc={Up:38,Down:40,Left:37,Right:39,Enter:13,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,"U+007F":46,Home:36,End:35,PageUp:33,PageDown:34,Insert:45},yc=w||y&&B("525"),zc=A&&x;
-P.prototype.Rb=function(a){y&&(17==this.t&&!a.ctrlKey||18==this.t&&!a.altKey||A&&91==this.t&&!a.metaKey)&&(this.N=this.t=-1);-1==this.t&&(a.ctrlKey&&17!=a.keyCode?this.t=17:a.altKey&&18!=a.keyCode?this.t=18:a.metaKey&&91!=a.keyCode&&(this.t=91));yc&&!tc(a.keyCode,this.t,a.shiftKey,a.ctrlKey,a.altKey)?this.handleEvent(a):(this.N=x?uc(a.keyCode):a.keyCode,zc&&(this.jb=a.altKey))};P.prototype.Sb=function(a){this.N=this.t=-1;this.jb=a.altKey};
-P.prototype.handleEvent=function(a){var b=a.P,c,d,e=b.altKey;w&&"keypress"==a.type?(c=this.N,d=13!=c&&27!=c?b.keyCode:0):y&&"keypress"==a.type?(c=this.N,d=0<=b.charCode&&63232>b.charCode&&sc(c)?b.charCode:0):Ra?(c=this.N,d=sc(c)?b.keyCode:0):(c=b.keyCode||this.N,d=b.charCode||0,zc&&(e=this.jb),A&&(63==d&&224==c)&&(c=191));var g=c,h=b.keyIdentifier;c?63232<=c&&c in wc?g=wc[c]:25==c&&a.shiftKey&&(g=9):h&&h in xc&&(g=xc[h]);a=g==this.t;this.t=g;b=new Ac(g,d,a,b);b.altKey=e;this.dispatchEvent(b)};
-P.prototype.a=function(){return this.c};var vc=function(a,b,c){a.Fa&&a.detach();a.c=b;a.Ea=H(a.c,"keypress",a,c);a.Xa=H(a.c,"keydown",a.Rb,c,a);a.Fa=H(a.c,"keyup",a.Sb,c,a)};P.prototype.detach=function(){this.Ea&&(J(this.Ea),J(this.Xa),J(this.Fa),this.Fa=this.Xa=this.Ea=null);this.c=null;this.N=this.t=-1};var Ac=function(a,b,c,d){d&&Cb(this,d,void 0);this.type="key";this.keyCode=a;this.charCode=b;this.repeat=c};r(Ac,G);var Cc=function(a,b){if(!a)throw Error("Invalid class name "+a);if(!n(b))throw Error("Invalid decorator function "+b);Bc[a]=b},Dc={},Bc={};var Q=function(){};ba(Q);Q.prototype.V=function(){};var Ec=function(a,b){a&&(a.tabIndex=b?0:-1)};f=Q.prototype;f.r=function(a){return a.kb().r("div",this.ta(a).join(" "))};f.C=function(a){return a};f.Z=function(a){return"DIV"==a.tagName};f.K=function(a,b){b.id&&fc(a,b.id);var c=this.A(),d=!1,e=C(b);e&&ya(e,function(b){b==c?d=!0:b&&this.ab(a,b,c)},this);d||D(b,c);Fc(a,this.C(b));return b};f.ab=function(a,b,c){b==c+"-disabled"?a.pa(!1):b==c+"-horizontal"?Gc(a,"horizontal"):b==c+"-vertical"&&Gc(a,"vertical")};
-var Fc=function(a,b){if(b)for(var c=b.firstChild,d;c&&c.parentNode==b;){d=c.nextSibling;if(1==c.nodeType){var e;t:{e=void 0;for(var g=C(c),h=0,k=g.length;h<k;h++)if(e=g[h]in Bc?Bc[g[h]]():null)break t;e=null}e&&(e.c=c,a.isEnabled()||e.pa(!1),a.Ca(e),e.K(c))}else c.nodeValue&&""!=na(c.nodeValue)||b.removeChild(c);c=d}};Q.prototype.Ma=function(a){a=a.a();s(a,"The container DOM element cannot be null.");ac(a,!0,x);w&&(a.hideFocus=!0);var b=this.V();b&&pc(a,b)};Q.prototype.k=function(a){return a.a()};
-Q.prototype.A=function(){return"goog-container"};Q.prototype.ta=function(a){var b=this.A(),c=[b,"horizontal"==a.L?b+"-horizontal":b+"-vertical"];a.isEnabled()||c.push(b+"-disabled");return c};var R=function(){},Hc;ba(R);f=R.prototype;f.V=function(){};f.r=function(a){var b=a.kb().r("div",this.ta(a).join(" "),a.Ba);Ic(a,b);return b};f.C=function(a){return a};f.ra=function(a,b,c){if(a=a.a?a.a():a)if(w&&!B("7")){var d=Jc(C(a),b);d.push(b);ia(c?D:E,a).apply(null,d)}else c?D(a,b):E(a,b)};f.Z=function(){return!0};
-f.K=function(a,b){b.id&&fc(a,b.id);var c=this.C(b);c&&c.firstChild?Kc(a,c.firstChild.nextSibling?Ca(c.childNodes):c.firstChild):a.Ba=null;var d=0,e=this.A(),g=this.A(),h=!1,k=!1,c=!1,q=C(b);ya(q,function(a){if(h||a!=e)if(k||a!=g){var b=d;this.tb||(this.Ha||Lc(this),this.tb=Ia(this.Ha));a=parseInt(this.tb[a],10);d=b|(isNaN(a)?0:a)}else k=!0;else h=!0,g==e&&(k=!0)},this);a.g=d;h||(q.push(e),g==e&&(k=!0));k||q.push(g);var ma=a.H;ma&&q.push.apply(q,ma);if(w&&!B("7")){var z=Jc(q);0<z.length&&(q.push.apply(q,
-z),c=!0)}if(!h||!k||ma||c)b.className=q.join(" ");Ic(a,b);return b};f.Ma=function(a){kc(a)&&this.qa(a.a(),!0);a.isEnabled()&&this.na(a,a.u())};var Mc=function(a,b,c){if(a=c||a.V())s(b,"The element passed as a first parameter cannot be null."),pc(b,a)},Ic=function(a,b){s(a);s(b);a.u()||rc(b,"hidden",!a.u());a.isEnabled()||Nc(b,1,!a.isEnabled());a.m&8&&Nc(b,8,!!(a.g&8));a.m&16&&Nc(b,16,!!(a.g&16));a.m&64&&Nc(b,64,!!(a.g&64))};f=R.prototype;f.za=function(a,b){ac(a,!b,!w&&!Ra)};
-f.qa=function(a,b){this.ra(a,this.A()+"-rtl",b)};f.S=function(a){var b;return a.m&32&&(b=a.k())?vb(b):!1};f.na=function(a,b){var c;if(a.m&32&&(c=a.k())){if(!b&&a.g&32){try{c.blur()}catch(d){}a.g&32&&a.la(null)}vb(c)!=b&&(b?c.tabIndex=0:(c.tabIndex=-1,c.removeAttribute("tabIndex")))}};f.ja=function(a,b){M(a,b);a&&rc(a,"hidden",!b)};f.v=function(a,b,c){var d=a.a();if(d){var e=Oc(this,b);e&&this.ra(a,e,c);Nc(d,b,c)}};
-var Nc=function(a,b,c){Hc||(Hc={1:"disabled",8:"selected",16:"checked",64:"expanded"});if(b=Hc[b])s(a,"The element passed as a first parameter cannot be null."),rc(a,b,c)};R.prototype.k=function(a){return a.a()};R.prototype.A=function(){return"goog-control"};R.prototype.ta=function(a){var b=this.A(),c=[b],d=this.A();d!=b&&c.push(d);b=a.g;for(d=[];b;){var e=b&-b;d.push(Oc(this,e));b&=~e}c.push.apply(c,d);(a=a.H)&&c.push.apply(c,a);w&&!B("7")&&c.push.apply(c,Jc(c));return c};
-var Jc=function(a,b){var c=[];b&&(a=a.concat([b]));ya([],function(d){!Aa(d,ia(u,a))||b&&!u(d,b)||c.push(d.join("_"))});return c},Oc=function(a,b){a.Ha||Lc(a);return a.Ha[b]},Lc=function(a){var b=a.A();a.Ha={1:b+"-disabled",2:b+"-hover",4:b+"-active",8:b+"-selected",16:b+"-checked",32:b+"-focused",64:b+"-open"}};var S=function(a,b,c){N.call(this,c);if(!b){b=this.constructor;for(var d;b;){d=p(b);if(d=Dc[d])break;b=b.f?b.f.constructor:null}b=d?n(d.aa)?d.aa():new d:null}this.b=b;this.Ba=void 0!==a?a:null};r(S,N);f=S.prototype;f.Ba=null;f.g=0;f.m=39;f.dc=255;f.W=0;f.q=!0;f.H=null;f.ca=!0;f.xa=!1;f.qb=null;f.pb=function(){return this.ca};f.Na=function(a){this.e&&a!=this.ca&&Pc(this,a);this.ca=a};f.k=function(){return this.b.k(this)};f.ya=function(){return this.ga||(this.ga=new P)};f.zb=function(){return this.b};
-f.ra=function(a,b){b?a&&(this.H?u(this.H,a)||this.H.push(a):this.H=[a],this.b.ra(this,a,!0)):a&&(this.H&&Ba(this.H,a))&&(0==this.H.length&&(this.H=null),this.b.ra(this,a,!1))};f.r=function(){var a=this.b.r(this);this.c=a;Mc(this.b,a,this.qb);this.xa||this.b.za(a,!1);this.u()||this.b.ja(a,!1)};f.C=function(){return this.b.C(this.a())};f.Z=function(a){return this.b.Z(a)};f.Wa=function(a){this.c=a=this.b.K(this,a);Mc(this.b,a,this.qb);this.xa||this.b.za(a,!1);this.q="none"!=a.style.display};
-f.G=function(){S.f.G.call(this);this.b.Ma(this);if(this.m&-2&&(this.pb()&&Pc(this,!0),this.m&32)){var a=this.k();if(a){var b=this.ya();vc(b,a);gc(this).d(b,"key",this.J).d(a,"focus",this.ma).d(a,"blur",this.la)}}};
-var Pc=function(a,b){var c=gc(a),d=a.a();b?(c.d(d,"mouseover",a.Qa).d(d,"mousedown",a.ka).d(d,"mouseup",a.Ra).d(d,"mouseout",a.Pa),a.oa!=aa&&c.d(d,"contextmenu",a.oa),w&&c.d(d,"dblclick",a.sb)):(c.w(d,"mouseover",a.Qa).w(d,"mousedown",a.ka).w(d,"mouseup",a.Ra).w(d,"mouseout",a.Pa),a.oa!=aa&&c.w(d,"contextmenu",a.oa),w&&c.w(d,"dblclick",a.sb))};S.prototype.da=function(){S.f.da.call(this);this.ga&&this.ga.detach();this.u()&&this.isEnabled()&&this.b.na(this,!1)};var Kc=function(a,b){a.Ba=b};f=S.prototype;
+typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";else if("function"==b&&"undefined"==typeof a.call)return"object";return b},da=function(a){return"array"==ca(a)},ea=function(a){var b=ca(a);return"array"==b||"object"==b&&"number"==typeof a.length},m=function(a){return"string"==typeof a},n=function(a){return"function"==ca(a)},fa=function(a){var b=typeof a;return"object"==b&&null!=a||"function"==b},ia=function(a){return a[ga]||
+(a[ga]=++ha)},ga="closure_uid_"+(1E9*Math.random()>>>0),ha=0,ja=function(a,b){var c=Array.prototype.slice.call(arguments,1);return function(){var b=c.slice();b.push.apply(b,arguments);return a.apply(this,b)}},la=function(a,b){var c=a.split("."),d=l;c[0]in d||!d.execScript||d.execScript("var "+c[0]);for(var e;c.length&&(e=c.shift());)c.length||void 0===b?d=d[e]?d[e]:d[e]={}:d[e]=b},p=function(a,b){function c(){}c.prototype=b.prototype;a.f=b.prototype;a.prototype=new c;a.prototype.constructor=a};var ma=function(a){Error.captureStackTrace?Error.captureStackTrace(this,ma):this.stack=Error().stack||"";a&&(this.message=String(a))};p(ma,Error);ma.prototype.name="CustomError";var na=function(a,b){for(var c=a.split("%s"),d="",e=Array.prototype.slice.call(arguments,1);e.length&&1<c.length;)d+=c.shift()+e.shift();return d+c.join("%s")},oa=function(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")},ua=function(a){if(!pa.test(a))return a;-1!=a.indexOf("&")&&(a=a.replace(qa,"&amp;"));-1!=a.indexOf("<")&&(a=a.replace(ra,"&lt;"));-1!=a.indexOf(">")&&(a=a.replace(sa,"&gt;"));-1!=a.indexOf('"')&&(a=a.replace(ta,"&quot;"));return a},qa=/&/g,ra=/</g,sa=/>/g,ta=/\"/g,pa=/[&<>\"]/;var va=function(a,b){b.unshift(a);ma.call(this,na.apply(null,b));b.shift()};p(va,ma);va.prototype.name="AssertionError";var wa=function(a,b,c){var d="Assertion failed";if(b)var d=d+(": "+b),e=c;else a&&(d+=": "+a,e=null);throw new va(""+d,e||[]);},r=function(a,b,c){a||wa("",b,Array.prototype.slice.call(arguments,2))},xa=function(a,b,c,d){a instanceof b||wa("instanceof check failed.",c,Array.prototype.slice.call(arguments,3))};var s=Array.prototype,ya=s.indexOf?function(a,b,c){r(null!=a.length);return s.indexOf.call(a,b,c)}:function(a,b,c){c=null==c?0:0>c?Math.max(0,a.length+c):c;if(m(a))return m(b)&&1==b.length?a.indexOf(b,c):-1;for(;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},za=s.forEach?function(a,b,c){r(null!=a.length);s.forEach.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=m(a)?a.split(""):a,g=0;g<d;g++)g in e&&b.call(c,e[g],g,a)},Aa=s.filter?function(a,b,c){r(null!=a.length);return s.filter.call(a,
+b,c)}:function(a,b,c){for(var d=a.length,e=[],g=0,h=m(a)?a.split(""):a,k=0;k<d;k++)if(k in h){var q=h[k];b.call(c,q,k,a)&&(e[g++]=q)}return e},Ba=s.every?function(a,b,c){r(null!=a.length);return s.every.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=m(a)?a.split(""):a,g=0;g<d;g++)if(g in e&&!b.call(c,e[g],g,a))return!1;return!0},t=function(a,b){return 0<=ya(a,b)},Ca=function(a,b){var c=ya(a,b),d;if(d=0<=c)r(null!=a.length),s.splice.call(a,c,1);return d},Da=function(a){var b=a.length;if(0<b){for(var c=
+Array(b),d=0;d<b;d++)c[d]=a[d];return c}return[]},Fa=function(a,b,c,d){r(null!=a.length);s.splice.apply(a,Ea(arguments,1))},Ea=function(a,b,c){r(null!=a.length);return 2>=arguments.length?s.slice.call(a,b):s.slice.call(a,b,c)};var Ga=function(a,b){for(var c in a)b.call(void 0,a[c],c,a)},Ha=function(a,b){for(var c in a)if(a[c]==b)return!0;return!1},Ia=function(a,b,c){if(b in a)throw Error('The object already contains the key "'+b+'"');a[b]=c},Ja=function(a){var b={},c;for(c in a)b[a[c]]=c;return b},Ka="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),La=function(a,b){for(var c,d,e=1;e<arguments.length;e++){d=arguments[e];for(c in d)a[c]=d[c];for(var g=0;g<Ka.length;g++)c=
+Ka[g],Object.prototype.hasOwnProperty.call(d,c)&&(a[c]=d[c])}};var u,Ma,Na,Oa,Pa=function(){return l.navigator?l.navigator.userAgent:null};Oa=Na=Ma=u=!1;var Qa;if(Qa=Pa()){var Ra=l.navigator;u=0==Qa.lastIndexOf("Opera",0);Ma=!u&&(-1!=Qa.indexOf("MSIE")||-1!=Qa.indexOf("Trident"));Na=!u&&-1!=Qa.indexOf("WebKit");Oa=!u&&!Na&&!Ma&&"Gecko"==Ra.product}var Sa=u,v=Ma,w=Oa,x=Na,Ta=l.navigator,y=-1!=(Ta&&Ta.platform||"").indexOf("Mac"),Ua=function(){var a=l.document;return a?a.documentMode:void 0},Va;
+t:{var Wa="",Xa;if(Sa&&l.opera)var Ya=l.opera.version,Wa="function"==typeof Ya?Ya():Ya;else if(w?Xa=/rv\:([^\);]+)(\)|;)/:v?Xa=/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/:x&&(Xa=/WebKit\/(\S+)/),Xa)var Za=Xa.exec(Pa()),Wa=Za?Za[1]:"";if(v){var $a=Ua();if($a>parseFloat(Wa)){Va=String($a);break t}}Va=Wa}
+var ab=Va,bb={},z=function(a){var b;if(!(b=bb[a])){b=0;for(var c=oa(String(ab)).split("."),d=oa(String(a)).split("."),e=Math.max(c.length,d.length),g=0;0==b&&g<e;g++){var h=c[g]||"",k=d[g]||"",q=RegExp("(\\d*)(\\D*)","g"),ka=RegExp("(\\d*)(\\D*)","g");do{var A=q.exec(h)||["","",""],I=ka.exec(k)||["","",""];if(0==A[0].length&&0==I[0].length)break;b=((0==A[1].length?0:parseInt(A[1],10))<(0==I[1].length?0:parseInt(I[1],10))?-1:(0==A[1].length?0:parseInt(A[1],10))>(0==I[1].length?0:parseInt(I[1],10))?
+1:0)||((0==A[2].length)<(0==I[2].length)?-1:(0==A[2].length)>(0==I[2].length)?1:0)||(A[2]<I[2]?-1:A[2]>I[2]?1:0)}while(0==b)}b=bb[a]=0<=b}return b},cb=l.document,db=cb&&v?Ua()||("CSS1Compat"==cb.compatMode?parseInt(ab,10):5):void 0;var eb,fb=!v||v&&9<=db;!w&&!v||v&&v&&9<=db||w&&z("1.9.1");var gb=v&&!z("9");var B=function(a){a=a.className;return m(a)&&a.match(/\S+/g)||[]},C=function(a,b){for(var c=B(a),d=Ea(arguments,1),e=c.length+d.length,g=c,h=0;h<d.length;h++)t(g,d[h])||g.push(d[h]);a.className=c.join(" ");return c.length==e},ib=function(a,b){var c=B(a),d=Ea(arguments,1),e=hb(c,d);a.className=e.join(" ");return e.length==c.length-d.length},hb=function(a,b){return Aa(a,function(a){return!t(b,a)})};var lb=function(a){return a?new jb(kb(a)):eb||(eb=new jb)},mb=function(a,b){return m(b)?a.getElementById(b):b},nb=function(a,b,c){var d=document;c=c||d;a=a&&"*"!=a?a.toUpperCase():"";if(c.querySelectorAll&&c.querySelector&&(a||b))return c.querySelectorAll(a+(b?"."+b:""));if(b&&c.getElementsByClassName){c=c.getElementsByClassName(b);if(a){for(var d={},e=0,g=0,h;h=c[g];g++)a==h.nodeName&&(d[e++]=h);d.length=e;return d}return c}c=c.getElementsByTagName(a||"*");if(b){d={};for(g=e=0;h=c[g];g++)a=h.className,
+"function"==typeof a.split&&t(a.split(/\s+/),b)&&(d[e++]=h);d.length=e;return d}return c},pb=function(a,b){Ga(b,function(b,d){"style"==d?a.style.cssText=b:"class"==d?a.className=b:"for"==d?a.htmlFor=b:d in ob?a.setAttribute(ob[d],b):0==d.lastIndexOf("aria-",0)||0==d.lastIndexOf("data-",0)?a.setAttribute(d,b):a[d]=b})},ob={cellpadding:"cellPadding",cellspacing:"cellSpacing",colspan:"colSpan",frameborder:"frameBorder",height:"height",maxlength:"maxLength",role:"role",rowspan:"rowSpan",type:"type",usemap:"useMap",
+valign:"vAlign",width:"width"},rb=function(a,b,c){return qb(document,arguments)},qb=function(a,b){var c=b[0],d=b[1];if(!fb&&d&&(d.name||d.type)){c=["<",c];d.name&&c.push(' name="',ua(d.name),'"');if(d.type){c.push(' type="',ua(d.type),'"');var e={};La(e,d);delete e.type;d=e}c.push(">");c=c.join("")}c=a.createElement(c);d&&(m(d)?c.className=d:da(d)?C.apply(null,[c].concat(d)):pb(c,d));2<b.length&&sb(a,c,b);return c},sb=function(a,b,c){function d(c){c&&b.appendChild(m(c)?a.createTextNode(c):c)}for(var e=
+2;e<c.length;e++){var g=c[e];if(!ea(g)||fa(g)&&0<g.nodeType)d(g);else{var h;t:{if(g&&"number"==typeof g.length){if(fa(g)){h="function"==typeof g.item||"string"==typeof g.item;break t}if(n(g)){h="function"==typeof g.item;break t}}h=!1}za(h?Da(g):g,d)}}},tb=function(a,b){if(a.contains&&1==b.nodeType)return a==b||a.contains(b);if("undefined"!=typeof a.compareDocumentPosition)return a==b||Boolean(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a},kb=function(a){return 9==a.nodeType?
+a:a.ownerDocument||a.document},ub=function(a,b){if("textContent"in a)a.textContent=b;else if(a.firstChild&&3==a.firstChild.nodeType){for(;a.lastChild!=a.firstChild;)a.removeChild(a.lastChild);a.firstChild.data=b}else{for(var c;c=a.firstChild;)a.removeChild(c);a.appendChild(kb(a).createTextNode(String(b)))}},vb={SCRIPT:1,STYLE:1,HEAD:1,IFRAME:1,OBJECT:1},wb={IMG:" ",BR:"\n"},xb=function(a){a=a.getAttributeNode("tabindex");return null!=a&&a.specified},yb=function(a){a=a.tabIndex;return"number"==typeof a&&
+0<=a&&32768>a},zb=function(a,b,c){if(!(a.nodeName in vb))if(3==a.nodeType)c?b.push(String(a.nodeValue).replace(/(\r\n|\r|\n)/g,"")):b.push(a.nodeValue);else if(a.nodeName in wb)b.push(wb[a.nodeName]);else for(a=a.firstChild;a;)zb(a,b,c),a=a.nextSibling},jb=function(a){this.F=a||l.document||document};f=jb.prototype;f.kb=lb;f.a=function(a){return mb(this.F,a)};f.r=function(a,b,c){return qb(this.F,arguments)};f.createElement=function(a){return this.F.createElement(a)};f.createTextNode=function(a){return this.F.createTextNode(String(a))};
+f.appendChild=function(a,b){a.appendChild(b)};f.contains=tb;f.J=function(a){var b;(b="INPUT"==a.tagName||"TEXTAREA"==a.tagName||"SELECT"==a.tagName||"BUTTON"==a.tagName?!a.disabled&&(!xb(a)||yb(a)):xb(a)&&yb(a))&&v?(a=n(a.getBoundingClientRect)?a.getBoundingClientRect():{height:a.offsetHeight,width:a.offsetWidth},a=null!=a&&0<a.height&&0<a.width):a=b;return a};var Ab=function(a){Ab[" "](a);return a};Ab[" "]=aa;var Bb=!v||v&&9<=db,Cb=!v||v&&9<=db,Db=v&&!z("9");!x||z("528");w&&z("1.9b")||v&&z("8")||Sa&&z("9.5")||x&&z("528");w&&!z("8")||v&&z("9");var Eb=function(){};Eb.prototype.Sb=!1;var D=function(a,b){this.type=a;this.currentTarget=this.target=b};f=D.prototype;f.S=!1;f.defaultPrevented=!1;f.xb=!0;f.stopPropagation=function(){this.S=!0};f.preventDefault=function(){this.defaultPrevented=!0;this.xb=!1};var E=function(a,b){a&&Fb(this,a,b)};p(E,D);var Gb=[1,4,2];f=E.prototype;f.target=null;f.relatedTarget=null;f.offsetX=0;f.offsetY=0;f.clientX=0;f.clientY=0;f.screenX=0;f.screenY=0;f.button=0;f.keyCode=0;f.charCode=0;f.ctrlKey=!1;f.altKey=!1;f.shiftKey=!1;f.metaKey=!1;f.bb=!1;f.Q=null;
+var Fb=function(a,b,c){var d=a.type=b.type;D.call(a,d);a.target=b.target||b.srcElement;a.currentTarget=c;if(c=b.relatedTarget){if(w){var e;t:{try{Ab(c.nodeName);e=!0;break t}catch(g){}e=!1}e||(c=null)}}else"mouseover"==d?c=b.fromElement:"mouseout"==d&&(c=b.toElement);a.relatedTarget=c;a.offsetX=x||void 0!==b.offsetX?b.offsetX:b.layerX;a.offsetY=x||void 0!==b.offsetY?b.offsetY:b.layerY;a.clientX=void 0!==b.clientX?b.clientX:b.pageX;a.clientY=void 0!==b.clientY?b.clientY:b.pageY;a.screenX=b.screenX||
+0;a.screenY=b.screenY||0;a.button=b.button;a.keyCode=b.keyCode||0;a.charCode=b.charCode||("keypress"==d?b.keyCode:0);a.ctrlKey=b.ctrlKey;a.altKey=b.altKey;a.shiftKey=b.shiftKey;a.metaKey=b.metaKey;a.bb=y?b.metaKey:b.ctrlKey;a.state=b.state;a.Q=b;b.defaultPrevented&&a.preventDefault();delete a.S},Hb=function(a){return Bb?0==a.Q.button:"click"==a.type?!0:!!(a.Q.button&Gb[0])};
+E.prototype.stopPropagation=function(){E.f.stopPropagation.call(this);this.Q.stopPropagation?this.Q.stopPropagation():this.Q.cancelBubble=!0};E.prototype.preventDefault=function(){E.f.preventDefault.call(this);var a=this.Q;if(a.preventDefault)a.preventDefault();else if(a.returnValue=!1,Db)try{if(a.ctrlKey||112<=a.keyCode&&123>=a.keyCode)a.keyCode=-1}catch(b){}};var Ib="closure_listenable_"+(1E6*Math.random()|0),Jb=function(a){try{return!(!a||!a[Ib])}catch(b){return!1}},Kb=0;var Lb=function(a,b,c,d,e){this.$=a;this.Da=null;this.src=b;this.type=c;this.capture=!!d;this.Ia=e;this.key=++Kb;this.ha=this.Ga=!1},Mb=function(a){a.ha=!0;a.$=null;a.Da=null;a.src=null;a.Ia=null};var F=function(a){this.src=a;this.p={};this.va=0};F.prototype.add=function(a,b,c,d,e){var g=this.p[a];g||(g=this.p[a]=[],this.va++);var h=Nb(g,b,d,e);-1<h?(a=g[h],c||(a.Ga=!1)):(a=new Lb(b,this.src,a,!!d,e),a.Ga=c,g.push(a));return a};F.prototype.remove=function(a,b,c,d){if(!(a in this.p))return!1;var e=this.p[a];b=Nb(e,b,c,d);return-1<b?(Mb(e[b]),r(null!=e.length),s.splice.call(e,b,1),0==e.length&&(delete this.p[a],this.va--),!0):!1};
+var Ob=function(a,b){var c=b.type;if(!(c in a.p))return!1;var d=Ca(a.p[c],b);d&&(Mb(b),0==a.p[c].length&&(delete a.p[c],a.va--));return d};F.prototype.$a=function(a){var b=0,c;for(c in this.p)if(!a||c==a){for(var d=this.p[c],e=0;e<d.length;e++)++b,Mb(d[e]);delete this.p[c];this.va--}return b};F.prototype.wa=function(a,b,c,d){a=this.p[a];var e=-1;a&&(e=Nb(a,b,c,d));return-1<e?a[e]:null};var Nb=function(a,b,c,d){for(var e=0;e<a.length;++e){var g=a[e];if(!g.ha&&g.$==b&&g.capture==!!c&&g.Ia==d)return e}return-1};var Pb="closure_lm_"+(1E6*Math.random()|0),G={},Qb=0,H=function(a,b,c,d,e){if(da(b)){for(var g=0;g<b.length;g++)H(a,b[g],c,d,e);return null}c=Rb(c);if(Jb(a))a=a.d(b,c,d,e);else{if(!b)throw Error("Invalid event type");var g=!!d,h=Sb(a);h||(a[Pb]=h=new F(a));c=h.add(b,c,!1,d,e);c.Da||(d=Tb(),c.Da=d,d.src=a,d.$=c,a.addEventListener?a.addEventListener(b,d,g):a.attachEvent(b in G?G[b]:G[b]="on"+b,d),Qb++);a=c}return a},Tb=function(){var a=Ub,b=Cb?function(c){return a.call(b.src,b.$,c)}:function(c){c=a.call(b.src,
+b.$,c);if(!c)return c};return b},Vb=function(a,b,c,d,e){if(da(b))for(var g=0;g<b.length;g++)Vb(a,b[g],c,d,e);else c=Rb(c),Jb(a)?a.w(b,c,d,e):a&&(a=Sb(a))&&(b=a.wa(b,c,!!d,e))&&J(b)},J=function(a){if("number"==typeof a||!a||a.ha)return!1;var b=a.src;if(Jb(b))return Ob(b.ba,a);var c=a.type,d=a.Da;b.removeEventListener?b.removeEventListener(c,d,a.capture):b.detachEvent&&b.detachEvent(c in G?G[c]:G[c]="on"+c,d);Qb--;(c=Sb(b))?(Ob(c,a),0==c.va&&(c.src=null,b[Pb]=null)):Mb(a);return!0},Xb=function(a,b,
+c,d){var e=1;if(a=Sb(a))if(b=a.p[b])for(b=Da(b),a=0;a<b.length;a++){var g=b[a];g&&g.capture==c&&!g.ha&&(e&=!1!==Wb(g,d))}return Boolean(e)},Wb=function(a,b){var c=a.$,d=a.Ia||a.src;a.Ga&&J(a);return c.call(d,b)},Ub=function(a,b){if(a.ha)return!0;if(!Cb){var c;if(!(c=b))t:{c=["window","event"];for(var d=l,e;e=c.shift();)if(null!=d[e])d=d[e];else{c=null;break t}c=d}e=c;c=new E(e,this);d=!0;if(!(0>e.keyCode||void 0!=e.returnValue)){t:{var g=!1;if(0==e.keyCode)try{e.keyCode=-1;break t}catch(h){g=!0}if(g||
+void 0==e.returnValue)e.returnValue=!0}e=[];for(g=c.currentTarget;g;g=g.parentNode)e.push(g);for(var g=a.type,k=e.length-1;!c.S&&0<=k;k--)c.currentTarget=e[k],d&=Xb(e[k],g,!0,c);for(k=0;!c.S&&k<e.length;k++)c.currentTarget=e[k],d&=Xb(e[k],g,!1,c)}return d}return Wb(a,new E(b,this))},Sb=function(a){a=a[Pb];return a instanceof F?a:null},Yb="__closure_events_fn_"+(1E9*Math.random()>>>0),Rb=function(a){r(a,"Listener can not be null.");if(n(a))return a;r(a.handleEvent,"An object listener must have handleEvent method.");
+return a[Yb]||(a[Yb]=function(b){return a.handleEvent(b)})};var K=function(a){this.Db=a;this.La={}};p(K,Eb);var Zb=[];K.prototype.d=function(a,b,c,d,e){da(b)||(Zb[0]=b,b=Zb);for(var g=0;g<b.length;g++){var h=H(a,b[g],c||this,d||!1,e||this.Db||this);if(!h)break;this.La[h.key]=h}return this};K.prototype.w=function(a,b,c,d,e){if(da(b))for(var g=0;g<b.length;g++)this.w(a,b[g],c,d,e);else e=e||this.Db||this,c=Rb(c||this),d=!!d,b=Jb(a)?a.wa(b,c,d,e):a?(a=Sb(a))?a.wa(b,c,d,e):null:null,b&&(J(b),delete this.La[b.key]);return this};
+K.prototype.$a=function(){Ga(this.La,J);this.La={}};K.prototype.handleEvent=function(){throw Error("EventHandler.handleEvent not implemented");};var L=function(){this.ba=new F(this);this.bc=this};p(L,Eb);L.prototype[Ib]=!0;f=L.prototype;f.mb=null;f.Ya=function(a){this.mb=a};f.addEventListener=function(a,b,c,d){H(this,a,b,c,d)};f.removeEventListener=function(a,b,c,d){Vb(this,a,b,c,d)};
+f.dispatchEvent=function(a){$b(this);var b,c=this.mb;if(c){b=[];for(var d=1;c;c=c.mb)b.push(c),r(1E3>++d,"infinite loop")}c=this.bc;d=a.type||a;if(m(a))a=new D(a,c);else if(a instanceof D)a.target=a.target||c;else{var e=a;a=new D(d,c);La(a,e)}var e=!0,g;if(b)for(var h=b.length-1;!a.S&&0<=h;h--)g=a.currentTarget=b[h],e=ac(g,d,!0,a)&&e;a.S||(g=a.currentTarget=c,e=ac(g,d,!0,a)&&e,a.S||(e=ac(g,d,!1,a)&&e));if(b)for(h=0;!a.S&&h<b.length;h++)g=a.currentTarget=b[h],e=ac(g,d,!1,a)&&e;return e};
+f.d=function(a,b,c,d){$b(this);return this.ba.add(a,b,!1,c,d)};f.w=function(a,b,c,d){return this.ba.remove(a,b,c,d)};var ac=function(a,b,c,d){b=a.ba.p[b];if(!b)return!0;b=Da(b);for(var e=!0,g=0;g<b.length;++g){var h=b[g];if(h&&!h.ha&&h.capture==c){var k=h.$,q=h.Ia||h.src;h.Ga&&Ob(a.ba,h);e=!1!==k.call(q,d)&&e}}return e&&!1!=d.xb};L.prototype.wa=function(a,b,c,d){return this.ba.wa(a,b,c,d)};var $b=function(a){r(a.ba,"Event target is not initialized. Did you call the superclass (goog.events.EventTarget) constructor?")};var M=function(a,b){a.style.display=b?"":"none"},bc=w?"MozUserSelect":x?"WebkitUserSelect":null,cc=function(a,b,c){c=c?null:a.getElementsByTagName("*");if(bc){if(b=b?"none":"",a.style[bc]=b,c){a=0;for(var d;d=c[a];a++)d.style[bc]=b}}else if(v||Sa)if(b=b?"on":"",a.setAttribute("unselectable",b),c)for(a=0;d=c[a];a++)d.setAttribute("unselectable",b)};var dc=function(){};ba(dc);dc.prototype.ec=0;dc.aa();var N=function(a){L.call(this);this.n=a||lb();this.sa=ec};p(N,L);N.prototype.dc=dc.aa();var ec=null,fc=function(a,b){switch(a){case 1:return b?"disable":"enable";case 2:return b?"highlight":"unhighlight";case 4:return b?"activate":"deactivate";case 8:return b?"select":"unselect";case 16:return b?"check":"uncheck";case 32:return b?"focus":"blur";case 64:return b?"open":"close"}throw Error("Invalid component state");};f=N.prototype;f.P=null;f.e=!1;f.c=null;f.sa=null;f.o=null;f.s=null;f.i=null;
+var gc=function(a){return a.P||(a.P=":"+(a.dc.ec++).toString(36))},hc=function(a,b){if(a.o&&a.o.i){var c=a.o.i,d=a.P;d in c&&delete c[d];Ia(a.o.i,b,a)}a.P=b};N.prototype.a=function(){return this.c};var ic=function(a){return a.ib||(a.ib=new K(a))},jc=function(a,b){if(a==b)throw Error("Unable to set parent component");if(b&&a.o&&a.P&&a.o.i&&a.P&&a.P in a.o.i&&a.o.i[a.P]&&a.o!=b)throw Error("Unable to set parent component");a.o=b;N.f.Ya.call(a,b)};f=N.prototype;f.getParent=function(){return this.o};
+f.Ya=function(a){if(this.o&&this.o!=a)throw Error("Method not supported");N.f.Ya.call(this,a)};f.kb=function(){return this.n};f.r=function(){this.c=this.n.createElement("div")};f.L=function(a){if(this.e)throw Error("Component already rendered");if(a&&this.Z(a)){var b=kb(a);this.n&&this.n.F==b||(this.n=lb(a));this.Xa(a);this.G()}else throw Error("Invalid element to decorate");};f.Z=function(){return!0};f.Xa=function(a){this.c=a};f.G=function(){this.e=!0;kc(this,function(a){!a.e&&a.a()&&a.G()})};
+f.da=function(){kc(this,function(a){a.e&&a.da()});this.ib&&this.ib.$a();this.e=!1};f.Ca=function(a,b){this.Ta(a,lc(this),b)};
+f.Ta=function(a,b,c){if(a.e&&(c||!this.e))throw Error("Component already rendered");if(0>b||b>lc(this))throw Error("Child component index out of bounds");this.i&&this.s||(this.i={},this.s=[]);if(a.getParent()==this){var d=gc(a);this.i[d]=a;Ca(this.s,a)}else Ia(this.i,gc(a),a);jc(a,this);Fa(this.s,b,0,a);if(a.e&&this.e&&a.getParent()==this)c=this.C(),c.insertBefore(a.a(),c.childNodes[b]||null);else if(c){this.c||this.r();c=O(this,b+1);b=this.C();c=c?c.c:null;if(a.e)throw Error("Component already rendered");
+a.c||a.r();b?b.insertBefore(a.c,c||null):a.n.F.body.appendChild(a.c);a.o&&!a.o.e||a.G()}else this.e&&!a.e&&a.c&&a.c.parentNode&&1==a.c.parentNode.nodeType&&a.G()};f.C=function(){return this.c};
+var mc=function(a){if(null==a.sa){var b;t:{b=a.e?a.c:a.n.F.body;var c=kb(b);if(c.defaultView&&c.defaultView.getComputedStyle&&(b=c.defaultView.getComputedStyle(b,null))){b=b.direction||b.getPropertyValue("direction")||"";break t}b=""}a.sa="rtl"==(b||((a.e?a.c:a.n.F.body).currentStyle?(a.e?a.c:a.n.F.body).currentStyle.direction:null)||(a.e?a.c:a.n.F.body).style&&(a.e?a.c:a.n.F.body).style.direction)}return a.sa};
+N.prototype.qa=function(a){if(this.e)throw Error("Component already rendered");this.sa=a};var lc=function(a){return a.s?a.s.length:0},O=function(a,b){return a.s?a.s[b]||null:null},kc=function(a,b,c){a.s&&za(a.s,b,c)},nc=function(a,b){return a.s&&b?ya(a.s,b):-1};
+N.prototype.removeChild=function(a,b){if(a){var c=m(a)?a:gc(a);a=this.i&&c?(c in this.i?this.i[c]:void 0)||null:null;if(c&&a){var d=this.i;c in d&&delete d[c];Ca(this.s,a);b&&(a.da(),a.c&&(c=a.c)&&c.parentNode&&c.parentNode.removeChild(c));jc(a,null)}}if(!a)throw Error("Child is not in parent component");return a};var oc,pc={kc:"activedescendant",pc:"atomic",qc:"autocomplete",sc:"busy",vc:"checked",Ac:"controls",Cc:"describedby",Fc:"disabled",Hc:"dropeffect",Ic:"expanded",Jc:"flowto",Lc:"grabbed",Pc:"haspopup",Rc:"hidden",Tc:"invalid",Uc:"label",Vc:"labelledby",Wc:"level",ad:"live",ld:"multiline",md:"multiselectable",qd:"orientation",rd:"owns",sd:"posinset",ud:"pressed",yd:"readonly",Ad:"relevant",Bd:"required",Hd:"selected",Jd:"setsize",Ld:"sort",Yd:"valuemax",Zd:"valuemin",$d:"valuenow",ae:"valuetext"};var qc={lc:"alert",mc:"alertdialog",nc:"application",oc:"article",rc:"banner",tc:"button",uc:"checkbox",wc:"columnheader",xc:"combobox",yc:"complementary",zc:"contentinfo",Bc:"definition",Dc:"dialog",Ec:"directory",Gc:"document",Kc:"form",Mc:"grid",Nc:"gridcell",Oc:"group",Qc:"heading",Sc:"img",Xc:"link",Yc:"list",Zc:"listbox",$c:"listitem",bd:"log",cd:"main",dd:"marquee",ed:"math",fd:"menu",gd:"menubar",hd:"menuitem",jd:"menuitemcheckbox",kd:"menuitemradio",nd:"navigation",od:"note",pd:"option",
+td:"presentation",vd:"progressbar",wd:"radio",xd:"radiogroup",zd:"region",Cd:"row",Dd:"rowgroup",Ed:"rowheader",Fd:"scrollbar",Gd:"search",Id:"separator",Kd:"slider",Md:"spinbutton",Nd:"status",Od:"tab",Pd:"tablist",Qd:"tabpanel",Rd:"textbox",Sd:"timer",Td:"toolbar",Ud:"tooltip",Vd:"tree",Wd:"treegrid",Xd:"treeitem"};var rc=function(a,b){b?(r(Ha(qc,b),"No such ARIA role "+b),a.setAttribute("role",b)):a.removeAttribute("role")},tc=function(a,b,c){ea(c)&&(c=c.join(" "));var d=sc(b);""===c||void 0==c?(oc||(oc={atomic:!1,autocomplete:"none",dropeffect:"none",haspopup:!1,live:"off",multiline:!1,multiselectable:!1,orientation:"vertical",readonly:!1,relevant:"additions text",required:!1,sort:"none",busy:!1,disabled:!1,hidden:!1,invalid:"false"}),c=oc,b in c?a.setAttribute(d,c[b]):a.removeAttribute(d)):a.setAttribute(d,
+c)},sc=function(a){r(a,"ARIA attribute cannot be empty.");r(Ha(pc,a),"No such ARIA attribute "+a);return"aria-"+a};var vc=function(a,b,c,d,e){if(!(v||x&&z("525")))return!0;if(y&&e)return uc(a);if(e&&!d||!c&&(17==b||18==b||y&&91==b))return!1;if(x&&d&&c)switch(a){case 220:case 219:case 221:case 192:case 186:case 189:case 187:case 188:case 190:case 191:case 192:case 222:return!1}if(v&&d&&b==a)return!1;switch(a){case 13:return!(v&&v&&9<=db);case 27:return!x}return uc(a)},uc=function(a){if(48<=a&&57>=a||96<=a&&106>=a||65<=a&&90>=a||x&&0==a)return!0;switch(a){case 32:case 63:case 107:case 109:case 110:case 111:case 186:case 59:case 189:case 187:case 61:case 188:case 190:case 191:case 192:case 222:case 219:case 220:case 221:return!0;
+default:return!1}},wc=function(a){switch(a){case 61:return 187;case 59:return 186;case 173:return 189;case 224:return 91;case 0:return 224;default:return a}};var P=function(a,b){L.call(this);a&&xc(this,a,b)};p(P,L);f=P.prototype;f.c=null;f.Ea=null;f.Wa=null;f.Fa=null;f.t=-1;f.O=-1;f.jb=!1;
+var yc={3:13,12:144,63232:38,63233:40,63234:37,63235:39,63236:112,63237:113,63238:114,63239:115,63240:116,63241:117,63242:118,63243:119,63244:120,63245:121,63246:122,63247:123,63248:44,63272:46,63273:36,63275:35,63276:33,63277:34,63289:144,63302:45},zc={Up:38,Down:40,Left:37,Right:39,Enter:13,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,"U+007F":46,Home:36,End:35,PageUp:33,PageDown:34,Insert:45},Ac=v||x&&z("525"),Bc=y&&w;
+P.prototype.Qb=function(a){x&&(17==this.t&&!a.ctrlKey||18==this.t&&!a.altKey||y&&91==this.t&&!a.metaKey)&&(this.O=this.t=-1);-1==this.t&&(a.ctrlKey&&17!=a.keyCode?this.t=17:a.altKey&&18!=a.keyCode?this.t=18:a.metaKey&&91!=a.keyCode&&(this.t=91));Ac&&!vc(a.keyCode,this.t,a.shiftKey,a.ctrlKey,a.altKey)?this.handleEvent(a):(this.O=w?wc(a.keyCode):a.keyCode,Bc&&(this.jb=a.altKey))};P.prototype.Rb=function(a){this.O=this.t=-1;this.jb=a.altKey};
+P.prototype.handleEvent=function(a){var b=a.Q,c,d,e=b.altKey;v&&"keypress"==a.type?(c=this.O,d=13!=c&&27!=c?b.keyCode:0):x&&"keypress"==a.type?(c=this.O,d=0<=b.charCode&&63232>b.charCode&&uc(c)?b.charCode:0):Sa?(c=this.O,d=uc(c)?b.keyCode:0):(c=b.keyCode||this.O,d=b.charCode||0,Bc&&(e=this.jb),y&&63==d&&224==c&&(c=191));var g=c,h=b.keyIdentifier;c?63232<=c&&c in yc?g=yc[c]:25==c&&a.shiftKey&&(g=9):h&&h in zc&&(g=zc[h]);a=g==this.t;this.t=g;b=new Cc(g,d,a,b);b.altKey=e;this.dispatchEvent(b)};
+P.prototype.a=function(){return this.c};var xc=function(a,b,c){a.Fa&&a.detach();a.c=b;a.Ea=H(a.c,"keypress",a,c);a.Wa=H(a.c,"keydown",a.Qb,c,a);a.Fa=H(a.c,"keyup",a.Rb,c,a)};P.prototype.detach=function(){this.Ea&&(J(this.Ea),J(this.Wa),J(this.Fa),this.Fa=this.Wa=this.Ea=null);this.c=null;this.O=this.t=-1};var Cc=function(a,b,c,d){d&&Fb(this,d,void 0);this.type="key";this.keyCode=a;this.charCode=b;this.repeat=c};p(Cc,E);var Ec=function(a,b){if(!a)throw Error("Invalid class name "+a);if(!n(b))throw Error("Invalid decorator function "+b);Dc[a]=b},Fc={},Dc={};var Q=function(){};ba(Q);Q.prototype.V=function(){};var Gc=function(a,b){a&&(a.tabIndex=b?0:-1)};f=Q.prototype;f.r=function(a){return a.kb().r("div",this.ta(a).join(" "))};f.C=function(a){return a};f.Z=function(a){return"DIV"==a.tagName};f.L=function(a,b){b.id&&hc(a,b.id);var c=this.A(),d=!1,e=B(b);e&&za(e,function(b){b==c?d=!0:b&&this.ab(a,b,c)},this);d||C(b,c);Hc(a,this.C(b));return b};f.ab=function(a,b,c){b==c+"-disabled"?a.pa(!1):b==c+"-horizontal"?Ic(a,"horizontal"):b==c+"-vertical"&&Ic(a,"vertical")};
+var Hc=function(a,b){if(b)for(var c=b.firstChild,d;c&&c.parentNode==b;){d=c.nextSibling;if(1==c.nodeType){var e;t:{e=void 0;for(var g=B(c),h=0,k=g.length;h<k;h++)if(e=g[h]in Dc?Dc[g[h]]():null)break t;e=null}e&&(e.c=c,a.isEnabled()||e.pa(!1),a.Ca(e),e.L(c))}else c.nodeValue&&""!=oa(c.nodeValue)||b.removeChild(c);c=d}};Q.prototype.Ma=function(a){a=a.a();r(a,"The container DOM element cannot be null.");cc(a,!0,w);v&&(a.hideFocus=!0);var b=this.V();b&&rc(a,b)};Q.prototype.k=function(a){return a.a()};
+Q.prototype.A=function(){return"goog-container"};Q.prototype.ta=function(a){var b=this.A(),c=[b,"horizontal"==a.M?b+"-horizontal":b+"-vertical"];a.isEnabled()||c.push(b+"-disabled");return c};var R=function(){},Jc;ba(R);f=R.prototype;f.V=function(){};f.r=function(a){var b=a.kb().r("div",this.ta(a).join(" "),a.Ba);Kc(a,b);return b};f.C=function(a){return a};f.ra=function(a,b,c){if(a=a.a?a.a():a)if(v&&!z("7")){var d=Lc(B(a),b);d.push(b);ja(c?C:ib,a).apply(null,d)}else c?C(a,b):ib(a,b)};f.Z=function(){return!0};
+f.L=function(a,b){b.id&&hc(a,b.id);var c=this.C(b);c&&c.firstChild?Mc(a,c.firstChild.nextSibling?Da(c.childNodes):c.firstChild):a.Ba=null;var d=0,e=this.A(),g=this.A(),h=!1,k=!1,c=!1,q=B(b);za(q,function(a){if(h||a!=e)if(k||a!=g){var b=d;this.tb||(this.Ha||Nc(this),this.tb=Ja(this.Ha));a=parseInt(this.tb[a],10);d=b|(isNaN(a)?0:a)}else k=!0;else h=!0,g==e&&(k=!0)},this);a.g=d;h||(q.push(e),g==e&&(k=!0));k||q.push(g);var ka=a.H;ka&&q.push.apply(q,ka);if(v&&!z("7")){var A=Lc(q);0<A.length&&(q.push.apply(q,
+A),c=!0)}if(!h||!k||ka||c)b.className=q.join(" ");Kc(a,b);return b};f.Ma=function(a){mc(a)&&this.qa(a.a(),!0);a.isEnabled()&&this.na(a,a.u())};var Oc=function(a,b,c){if(a=c||a.V())r(b,"The element passed as a first parameter cannot be null."),rc(b,a)},Kc=function(a,b){r(a);r(b);a.u()||tc(b,"hidden",!a.u());a.isEnabled()||Pc(b,1,!a.isEnabled());a.m&8&&Pc(b,8,!!(a.g&8));a.m&16&&Pc(b,16,!!(a.g&16));a.m&64&&Pc(b,64,!!(a.g&64))};f=R.prototype;f.za=function(a,b){cc(a,!b,!v&&!Sa)};
+f.qa=function(a,b){this.ra(a,this.A()+"-rtl",b)};f.J=function(a){var b;return a.m&32&&(b=a.k())?xb(b)&&yb(b):!1};f.na=function(a,b){var c;if(a.m&32&&(c=a.k())){if(!b&&a.g&32){try{c.blur()}catch(d){}a.g&32&&a.la(null)}(xb(c)&&yb(c))!=b&&(b?c.tabIndex=0:(c.tabIndex=-1,c.removeAttribute("tabIndex")))}};f.ja=function(a,b){M(a,b);a&&tc(a,"hidden",!b)};f.v=function(a,b,c){var d=a.a();if(d){var e=Qc(this,b);e&&this.ra(a,e,c);Pc(d,b,c)}};
+var Pc=function(a,b,c){Jc||(Jc={1:"disabled",8:"selected",16:"checked",64:"expanded"});if(b=Jc[b])r(a,"The element passed as a first parameter cannot be null."),tc(a,b,c)};R.prototype.k=function(a){return a.a()};R.prototype.A=function(){return"goog-control"};R.prototype.ta=function(a){var b=this.A(),c=[b],d=this.A();d!=b&&c.push(d);b=a.g;for(d=[];b;){var e=b&-b;d.push(Qc(this,e));b&=~e}c.push.apply(c,d);(a=a.H)&&c.push.apply(c,a);v&&!z("7")&&c.push.apply(c,Lc(c));return c};
+var Lc=function(a,b){var c=[];b&&(a=a.concat([b]));za([],function(d){!Ba(d,ja(t,a))||b&&!t(d,b)||c.push(d.join("_"))});return c},Qc=function(a,b){a.Ha||Nc(a);return a.Ha[b]},Nc=function(a){var b=a.A();a.Ha={1:b+"-disabled",2:b+"-hover",4:b+"-active",8:b+"-selected",16:b+"-checked",32:b+"-focused",64:b+"-open"}};var S=function(a,b,c){N.call(this,c);if(!b){b=this.constructor;for(var d;b;){d=ia(b);if(d=Fc[d])break;b=b.f?b.f.constructor:null}b=d?n(d.aa)?d.aa():new d:null}this.b=b;this.Ba=void 0!==a?a:null};p(S,N);f=S.prototype;f.Ba=null;f.g=0;f.m=39;f.cc=255;f.W=0;f.q=!0;f.H=null;f.ca=!0;f.xa=!1;f.qb=null;f.pb=function(){return this.ca};f.Na=function(a){this.e&&a!=this.ca&&Rc(this,a);this.ca=a};f.k=function(){return this.b.k(this)};f.ya=function(){return this.ga||(this.ga=new P)};f.zb=function(){return this.b};
+f.ra=function(a,b){b?a&&(this.H?t(this.H,a)||this.H.push(a):this.H=[a],this.b.ra(this,a,!0)):a&&this.H&&Ca(this.H,a)&&(0==this.H.length&&(this.H=null),this.b.ra(this,a,!1))};f.r=function(){var a=this.b.r(this);this.c=a;Oc(this.b,a,this.qb);this.xa||this.b.za(a,!1);this.u()||this.b.ja(a,!1)};f.C=function(){return this.b.C(this.a())};f.Z=function(a){return this.b.Z(a)};f.Xa=function(a){this.c=a=this.b.L(this,a);Oc(this.b,a,this.qb);this.xa||this.b.za(a,!1);this.q="none"!=a.style.display};
+f.G=function(){S.f.G.call(this);this.b.Ma(this);if(this.m&-2&&(this.pb()&&Rc(this,!0),this.m&32)){var a=this.k();if(a){var b=this.ya();xc(b,a);ic(this).d(b,"key",this.K).d(a,"focus",this.ma).d(a,"blur",this.la)}}};
+var Rc=function(a,b){var c=ic(a),d=a.a();b?(c.d(d,"mouseover",a.Qa).d(d,"mousedown",a.ka).d(d,"mouseup",a.Ra).d(d,"mouseout",a.Pa),a.oa!=aa&&c.d(d,"contextmenu",a.oa),v&&c.d(d,"dblclick",a.sb)):(c.w(d,"mouseover",a.Qa).w(d,"mousedown",a.ka).w(d,"mouseup",a.Ra).w(d,"mouseout",a.Pa),a.oa!=aa&&c.w(d,"contextmenu",a.oa),v&&c.w(d,"dblclick",a.sb))};S.prototype.da=function(){S.f.da.call(this);this.ga&&this.ga.detach();this.u()&&this.isEnabled()&&this.b.na(this,!1)};var Mc=function(a,b){a.Ba=b};f=S.prototype;
 f.qa=function(a){S.f.qa.call(this,a);var b=this.a();b&&this.b.qa(b,a)};f.za=function(a){this.xa=a;var b=this.a();b&&this.b.za(b,a)};f.u=function(){return this.q};f.ja=function(a,b){if(b||this.q!=a&&this.dispatchEvent(a?"show":"hide")){var c=this.a();c&&this.b.ja(c,a);this.isEnabled()&&this.b.na(this,a);this.q=a;return!0}return!1};f.isEnabled=function(){return!(this.g&1)};
-f.pa=function(a){var b=this.getParent();b&&"function"==typeof b.isEnabled&&!b.isEnabled()||!T(this,1,!a)||(a||(this.setActive(!1),this.D(!1)),this.u()&&this.b.na(this,a),this.v(1,!a))};f.D=function(a){T(this,2,a)&&this.v(2,a)};f.setActive=function(a){T(this,4,a)&&this.v(4,a)};var Qc=function(a,b){T(a,8,b)&&a.v(8,b)},Rc=function(a,b){T(a,64,b)&&a.v(64,b)};S.prototype.v=function(a,b){this.m&a&&b!=!!(this.g&a)&&(this.b.v(this,a,b),this.g=b?this.g|a:this.g&~a)};
-var Sc=function(a,b,c){if(a.e&&a.g&b&&!c)throw Error("Component already rendered");!c&&a.g&b&&a.v(b,!1);a.m=c?a.m|b:a.m&~b},U=function(a,b){return!!(a.dc&b)&&!!(a.m&b)},T=function(a,b,c){return!!(a.m&b)&&!!(a.g&b)!=c&&(!(a.W&b)||a.dispatchEvent(dc(b,c)))&&!a.Tb};f=S.prototype;f.Qa=function(a){(!a.relatedTarget||!rb(this.a(),a.relatedTarget))&&(this.dispatchEvent("enter")&&this.isEnabled()&&U(this,2))&&this.D(!0)};
-f.Pa=function(a){a.relatedTarget&&rb(this.a(),a.relatedTarget)||!this.dispatchEvent("leave")||(U(this,4)&&this.setActive(!1),U(this,2)&&this.D(!1))};f.oa=aa;f.ka=function(a){this.isEnabled()&&(U(this,2)&&this.D(!0),!Eb(a)||y&&A&&a.ctrlKey||(U(this,4)&&this.setActive(!0),this.b.S(this)&&this.k().focus()));this.xa||(!Eb(a)||y&&A&&a.ctrlKey)||a.preventDefault()};f.Ra=function(a){this.isEnabled()&&(U(this,2)&&this.D(!0),this.g&4&&(Tc(this,a)&&U(this,4))&&this.setActive(!1))};
-f.sb=function(a){this.isEnabled()&&Tc(this,a)};var Tc=function(a,b){if(U(a,16)){var c=!(a.g&16);T(a,16,c)&&a.v(16,c)}U(a,8)&&Qc(a,!0);U(a,64)&&Rc(a,!(a.g&64));c=new F("action",a);b&&(c.altKey=b.altKey,c.ctrlKey=b.ctrlKey,c.metaKey=b.metaKey,c.shiftKey=b.shiftKey,c.bb=b.bb);return a.dispatchEvent(c)};S.prototype.ma=function(){U(this,32)&&T(this,32,!0)&&this.v(32,!0)};S.prototype.la=function(){U(this,4)&&this.setActive(!1);U(this,32)&&T(this,32,!1)&&this.v(32,!1)};
-S.prototype.J=function(a){return this.u()&&this.isEnabled()&&this.lb(a)?(a.preventDefault(),a.stopPropagation(),!0):!1};S.prototype.lb=function(a){return 13==a.keyCode&&Tc(this,a)};if(!n(S))throw Error("Invalid component class "+S);if(!n(R))throw Error("Invalid renderer class "+R);var Uc=p(S);Dc[Uc]=R;Cc("goog-control",function(){return new S(null)});var V=function(a,b,c){N.call(this,c);this.b=b||Q.aa();this.L=a||"vertical"};r(V,N);f=V.prototype;f.ub=null;f.ga=null;f.b=null;f.L=null;f.q=!0;f.X=!0;f.Za=!0;f.j=-1;f.h=null;f.ea=!1;f.Pb=!1;f.Ob=!0;f.M=null;f.k=function(){return this.ub||this.b.k(this)};f.ya=function(){return this.ga||(this.ga=new P(this.k()))};f.zb=function(){return this.b};f.r=function(){this.c=this.b.r(this)};f.C=function(){return this.b.C(this.a())};f.Z=function(a){return this.b.Z(a)};
-f.Wa=function(a){this.c=this.b.K(this,a);"none"==a.style.display&&(this.q=!1)};f.G=function(){V.f.G.call(this);ic(this,function(a){a.e&&Vc(this,a)},this);var a=this.a();this.b.Ma(this);this.ja(this.q,!0);gc(this).d(this,"enter",this.Ib).d(this,"highlight",this.Jb).d(this,"unhighlight",this.Lb).d(this,"open",this.Kb).d(this,"close",this.Gb).d(a,"mousedown",this.ka).d(ib(a),"mouseup",this.Hb).d(a,["mousedown","mouseup","mouseover","mouseout","contextmenu"],this.Fb);this.S()&&Wc(this,!0)};
-var Wc=function(a,b){var c=gc(a),d=a.k();b?c.d(d,"focus",a.ma).d(d,"blur",a.la).d(a.ya(),"key",a.J):c.w(d,"focus",a.ma).w(d,"blur",a.la).w(a.ya(),"key",a.J)};f=V.prototype;f.da=function(){Xc(this,-1);this.h&&Rc(this.h,!1);this.ea=!1;V.f.da.call(this)};f.Ib=function(){return!0};
-f.Jb=function(a){var b=lc(this,a.target);if(-1<b&&b!=this.j){var c=O(this,this.j);c&&c.D(!1);this.j=b;c=O(this,this.j);this.ea&&c.setActive(!0);this.Ob&&(this.h&&c!=this.h)&&(c.m&64?Rc(c,!0):Rc(this.h,!1))}b=this.a();s(b,"The DOM element for the container cannot be null.");null!=a.target.a()&&rc(b,"activedescendant",a.target.a().id)};f.Lb=function(a){a.target==O(this,this.j)&&(this.j=-1);a=this.a();s(a,"The DOM element for the container cannot be null.");a.removeAttribute(qc("activedescendant"))};
-f.Kb=function(a){(a=a.target)&&(a!=this.h&&a.getParent()==this)&&(this.h&&Rc(this.h,!1),this.h=a)};f.Gb=function(a){a.target==this.h&&(this.h=null)};f.ka=function(a){this.X&&(this.ea=!0);var b=this.k();b&&vb(b)?b.focus():a.preventDefault()};f.Hb=function(){this.ea=!1};
-f.Fb=function(a){var b;t:{b=a.target;if(this.M)for(var c=this.a();b&&b!==c;){var d=b.id;if(d in this.M){b=this.M[d];break t}b=b.parentNode}b=null}if(b)switch(a.type){case "mousedown":b.ka(a);break;case "mouseup":b.Ra(a);break;case "mouseover":b.Qa(a);break;case "mouseout":b.Pa(a);break;case "contextmenu":b.oa(a)}};f.ma=function(){};f.la=function(){Xc(this,-1);this.ea=!1;this.h&&Rc(this.h,!1)};
-f.J=function(a){return this.isEnabled()&&this.u()&&(0!=jc(this)||this.ub)&&this.lb(a)?(a.preventDefault(),a.stopPropagation(),!0):!1};
-f.lb=function(a){var b=O(this,this.j);if(b&&"function"==typeof b.J&&b.J(a)||this.h&&this.h!=b&&"function"==typeof this.h.J&&this.h.J(a))return!0;if(a.shiftKey||a.ctrlKey||a.metaKey||a.altKey)return!1;switch(a.keyCode){case 27:if(this.S())this.k().blur();else return!1;break;case 36:Yc(this);break;case 35:Zc(this);break;case 38:if("vertical"==this.L)$c(this);else return!1;break;case 37:if("horizontal"==this.L)kc(this)?ad(this):$c(this);else return!1;break;case 40:if("vertical"==this.L)ad(this);else return!1;
-break;case 39:if("horizontal"==this.L)kc(this)?$c(this):ad(this);else return!1;break;default:return!1}return!0};var Vc=function(a,b){var c=b.a(),c=c.id||(c.id=ec(b));a.M||(a.M={});a.M[c]=b};V.prototype.Ca=function(a,b){wa(a,S,"The child of a container must be a control");V.f.Ca.call(this,a,b)};V.prototype.Ta=function(a,b,c){a.W|=2;a.W|=64;!this.S()&&this.Pb||Sc(a,32,!1);a.Na(!1);V.f.Ta.call(this,a,b,c);a.e&&this.e&&Vc(this,a);b<=this.j&&this.j++};
-V.prototype.removeChild=function(a,b){if(a=m(a)?this.i&&a?(a in this.i?this.i[a]:void 0)||null:null:a){var c=lc(this,a);-1!=c&&(c==this.j?a.D(!1):c<this.j&&this.j--);var d=a.a();d&&(d.id&&this.M)&&(c=this.M,d=d.id,d in c&&delete c[d])}a=V.f.removeChild.call(this,a,b);a.Na(!0);return a};var Gc=function(a,b){if(a.a())throw Error("Component already rendered");a.L=b};f=V.prototype;f.u=function(){return this.q};
-f.ja=function(a,b){if(b||this.q!=a&&this.dispatchEvent(a?"show":"hide")){this.q=a;var c=this.a();c&&(M(c,a),this.S()&&Ec(this.k(),this.X&&this.q),b||this.dispatchEvent(this.q?"aftershow":"afterhide"));return!0}return!1};f.isEnabled=function(){return this.X};f.pa=function(a){this.X!=a&&this.dispatchEvent(a?"enable":"disable")&&(a?(this.X=!0,ic(this,function(a){a.vb?delete a.vb:a.pa(!0)})):(ic(this,function(a){a.isEnabled()?a.pa(!1):a.vb=!0}),this.ea=this.X=!1),this.S()&&Ec(this.k(),a&&this.q))};
-f.S=function(){return this.Za};f.na=function(a){a!=this.Za&&this.e&&Wc(this,a);this.Za=a;this.X&&this.q&&Ec(this.k(),a)};var Xc=function(a,b){var c=O(a,b);c?c.D(!0):-1<a.j&&O(a,a.j).D(!1)};V.prototype.D=function(a){Xc(this,lc(this,a))};
-var Yc=function(a){bd(a,function(a,c){return(a+1)%c},jc(a)-1)},Zc=function(a){bd(a,function(a,c){a--;return 0>a?c-1:a},0)},ad=function(a){bd(a,function(a,c){return(a+1)%c},a.j)},$c=function(a){bd(a,function(a,c){a--;return 0>a?c-1:a},a.j)},bd=function(a,b,c){c=0>c?lc(a,a.h):c;var d=jc(a);c=b.call(a,c,d);for(var e=0;e<=d;){var g=O(a,c);if(g&&g.u()&&g.isEnabled()&&g.m&2){a.Ua(c);break}e++;c=b.call(a,c,d)}};V.prototype.Ua=function(a){Xc(this,a)};var cd=function(){};r(cd,R);ba(cd);f=cd.prototype;f.A=function(){return"goog-tab"};f.V=function(){return"tab"};f.r=function(a){var b=cd.f.r.call(this,a);(a=a.Sa())&&this.Va(b,a);return b};f.K=function(a,b){b=cd.f.K.call(this,a,b);var c=this.Sa(b);c&&(a.rb=c);a.g&8&&(c=a.getParent())&&n(c.Y)&&(a.v(8,!1),c.Y(a));return b};f.Sa=function(a){return a.title||""};f.Va=function(a,b){a&&(a.title=b||"")};var dd=function(a,b,c){S.call(this,a,b||cd.aa(),c);Sc(this,8,!0);this.W|=9};r(dd,S);dd.prototype.Sa=function(){return this.rb};dd.prototype.Va=function(a){this.zb().Va(this.a(),a);this.rb=a};Cc("goog-tab",function(){return new dd(null)});var W=function(){};r(W,Q);ba(W);W.prototype.A=function(){return"goog-tab-bar"};W.prototype.V=function(){return"tablist"};W.prototype.ab=function(a,b,c){this.Ab||(this.Ja||ed(this),this.Ab=Ia(this.Ja));var d=this.Ab[b];d?(Gc(a,fd(d)),a.wb=d):W.f.ab.call(this,a,b,c)};W.prototype.ta=function(a){var b=W.f.ta.call(this,a);this.Ja||ed(this);b.push(this.Ja[a.wb]);return b};var ed=function(a){var b=a.A();a.Ja={top:b+"-top",bottom:b+"-bottom",start:b+"-start",end:b+"-end"}};var X=function(a,b,c){a=a||"top";Gc(this,fd(a));this.wb=a;V.call(this,this.L,b||W.aa(),c);gd(this)};r(X,V);f=X.prototype;f.$b=!0;f.I=null;f.G=function(){X.f.G.call(this);gd(this)};f.removeChild=function(a,b){hd(this,a);return X.f.removeChild.call(this,a,b)};f.Ua=function(a){X.f.Ua.call(this,a);this.$b&&this.Y(O(this,a))};f.Y=function(a){a?Qc(a,!0):this.I&&Qc(this.I,!1)};
-var hd=function(a,b){if(b&&b==a.I){for(var c=lc(a,b),d=c-1;b=O(a,d);d--)if(b.u()&&b.isEnabled()){a.Y(b);return}for(c+=1;b=O(a,c);c++)if(b.u()&&b.isEnabled()){a.Y(b);return}a.Y(null)}};f=X.prototype;f.Yb=function(a){this.I&&this.I!=a.target&&Qc(this.I,!1);this.I=a.target};f.Zb=function(a){a.target==this.I&&(this.I=null)};f.Wb=function(a){hd(this,a.target)};f.Xb=function(a){hd(this,a.target)};f.ma=function(){O(this,this.j)||this.D(this.I||O(this,0))};
-var gd=function(a){gc(a).d(a,"select",a.Yb).d(a,"unselect",a.Zb).d(a,"disable",a.Wb).d(a,"hide",a.Xb)},fd=function(a){return"start"==a||"end"==a?"vertical":"horizontal"};Cc("goog-tab-bar",function(){return new X});var Y=function(a,b,c,d,e){function g(a){a&&(a.tabIndex=0,pc(a,h.V()),D(a,"goog-zippy-header"),id(h,a),a&&h.Mb.d(a,"keydown",h.Nb))}L.call(this);this.n=e||jb();this.T=this.n.a(a)||null;this.Aa=this.n.a(d||null);this.fa=(this.Oa=n(b)?b:null)||!b?null:this.n.a(b);this.l=!0==c;this.Mb=new K(this);this.ob=new K(this);var h=this;g(this.T);g(this.Aa);this.U(this.l)};r(Y,L);f=Y.prototype;f.ca=!0;f.V=function(){return"tab"};f.C=function(){return this.fa};f.toggle=function(){this.U(!this.l)};
-f.U=function(a){this.fa?M(this.fa,a):a&&this.Oa&&(this.fa=this.Oa());this.fa&&D(this.fa,"goog-zippy-content");if(this.Aa)M(this.T,!a),M(this.Aa,a);else if(this.T){var b=this.T;a?D(b,"goog-zippy-expanded"):E(b,"goog-zippy-expanded");b=this.T;a?E(b,"goog-zippy-collapsed"):D(b,"goog-zippy-collapsed");rc(this.T,"expanded",a)}this.l=a;this.dispatchEvent(new jd("toggle",this))};f.pb=function(){return this.ca};f.Na=function(a){this.ca!=a&&((this.ca=a)?(id(this,this.T),id(this,this.Aa)):this.ob.$a())};
-var id=function(a,b){b&&a.ob.d(b,"click",a.ac)};Y.prototype.Nb=function(a){if(13==a.keyCode||32==a.keyCode)this.toggle(),this.dispatchEvent(new F("action",this)),a.preventDefault(),a.stopPropagation()};Y.prototype.ac=function(){this.toggle();this.dispatchEvent(new F("action",this))};var jd=function(a,b){F.call(this,a,b)};r(jd,F);var Z=function(a,b){this.nb=[];for(var c=lb("span","ae-zippy",kb(document,a)),d=0,e;e=c[d];d++){var g;if(void 0!=e.parentNode.parentNode.parentNode.nextElementSibling)g=e.parentNode.parentNode.parentNode.nextElementSibling;else for(g=e.parentNode.parentNode.parentNode.nextSibling;g&&1!=g.nodeType;)g=g.nextSibling;e=new Y(e,g,!1);this.nb.push(e)}this.gc=new kd(this.nb,kb(document,b))};Z.prototype.jc=function(){return this.gc};Z.prototype.kc=function(){return this.nb};
-var kd=function(a,b){this.ua=a;if(this.ua.length)for(var c=0,d;d=this.ua[c];c++)H(d,"toggle",this.Vb,!1,this);this.Ka=0;this.l=!1;c="ae-toggle ae-plus ae-action";this.ua.length||(c+=" ae-disabled");this.Q=pb("span",{className:c},"Expand All");H(this.Q,"click",this.Ub,!1,this);b&&b.appendChild(this.Q)};kd.prototype.Ub=function(){this.ua.length&&this.U(!this.l)};
-kd.prototype.Vb=function(a){a=a.currentTarget;this.Ka=a.l?this.Ka+1:this.Ka-1;a.l!=this.l&&(a.l?(this.l=!0,ld(this,!0)):0==this.Ka&&(this.l=!1,ld(this,!1)))};kd.prototype.U=function(a){this.l=a;a=0;for(var b;b=this.ua[a];a++)b.l!=this.l&&b.U(this.l);ld(this)};
-var ld=function(a,b){(void 0!==b?b:a.l)?(E(a.Q,"ae-plus"),D(a.Q,"ae-minus"),sb(a.Q,"Collapse All")):(E(a.Q,"ae-minus"),D(a.Q,"ae-plus"),sb(a.Q,"Expand All"))},md=function(a){this.bc=a;this.Cb={};var b,c=pb("div",{},b=pb("div",{id:"ae-stats-details-tabs",className:"goog-tab-bar goog-tab-bar-top"}),pb("div",{className:"goog-tab-bar-clear"}),a=pb("div",{id:"ae-stats-details-tabs-content",className:"goog-tab-content"})),d=new X;d.K(b);H(d,"select",this.Bb,!1,this);H(d,"unselect",this.Bb,!1,this);b=0;
-for(var e;e=this.bc[b];b++)if(e=kb(document,"ae-stats-details-"+e)){var g=lb("h2",null,e)[0],h;h=g;var k=void 0;fb&&"innerText"in h?k=h.innerText.replace(/(\r\n|\r|\n)/g,"\n"):(k=[],wb(h,k,!0),k=k.join(""));k=k.replace(/ \xAD /g," ").replace(/\xAD/g,"");k=k.replace(/\u200B/g,"");fb||(k=k.replace(/ +/g," "));" "!=k&&(k=k.replace(/^\s*/,""));h=k;g&&g.parentNode&&g.parentNode.removeChild(g);g=new dd(h);this.Cb[p(g)]=e;d.Ca(g,!0);a.appendChild(e);0==b?d.Y(g):M(e,!1)}kb(document,"bd").appendChild(c)};
-md.prototype.Bb=function(a){var b=this.Cb[p(a.target)];M(b,"select"==a.type)};ja("ae.Stats.Details.Tabs",md);ja("goog.ui.Zippy",Y);Y.prototype.setExpanded=Y.prototype.U;ja("ae.Stats.MakeZippys",Z);Z.prototype.getExpandCollapse=Z.prototype.jc;Z.prototype.getZippys=Z.prototype.kc;kd.prototype.setExpanded=kd.prototype.U;var $=function(){this.cb=[];this.hb=[]},nd=[[5,0.2,1],[6,0.2,1.2],[5,0.25,1.25],[6,0.25,1.5],[4,0.5,2],[5,0.5,2.5],[6,0.5,3],[4,1,4],[5,1,5],[6,1,6],[4,2,8],[5,2,10]],od=function(a){if(0>=a)return[2,0.5,1];for(var b=1;1>a;)a*=10,b/=10;for(;10<=a;)a/=10,b*=10;for(var c=0;c<nd.length;c++)if(a<=nd[c][2])return[nd[c][0],nd[c][1]*b,nd[c][2]*b];return[5,2*b,10*b]};$.prototype.gb="stats/static/pix.gif";$.prototype.B="ae-stats-gantt-";$.prototype.fb=0;$.prototype.write=function(a){this.hb.push(a)};
-var pd=function(a,b,c,d){a.write('<tr class="'+a.B+'axisrow"><td width="20%"></td><td>');a.write('<div class="'+a.B+'axis">');for(var e=0;e<=b;e++)a.write('<img class="'+a.B+'tick" src="'+a.gb+'" alt="" '),a.write('style="left:'+e*c*d+'%"\n>'),a.write('<span class="'+a.B+'scale" style="left:'+e*c*d+'%">'),a.write("&nbsp;"+e*c+"</span>");a.write("</div></td></tr>\n")};
-$.prototype.ic=function(){this.hb=[];var a=od(this.fb),b=a[0],c=a[1],a=100/a[2];this.write('<table class="'+this.B+'table">\n');pd(this,b,c,a);for(var d=0;d<this.cb.length;d++){var e=this.cb[d];this.write('<tr class="'+this.B+'datarow"><td width="20%">');0<e.label.length&&(0<e.ia.length&&this.write('<a class="'+this.B+'link" href="'+e.ia+'">'),this.write(e.label),0<e.ia.length&&this.write("</a>"));this.write("</td>\n<td>");this.write('<div class="'+this.B+'container">');0<e.ia.length&&this.write('<a class="'+
+f.pa=function(a){var b=this.getParent();b&&"function"==typeof b.isEnabled&&!b.isEnabled()||!T(this,1,!a)||(a||(this.setActive(!1),this.D(!1)),this.u()&&this.b.na(this,a),this.v(1,!a))};f.D=function(a){T(this,2,a)&&this.v(2,a)};f.setActive=function(a){T(this,4,a)&&this.v(4,a)};var Sc=function(a,b){T(a,8,b)&&a.v(8,b)},Tc=function(a,b){T(a,64,b)&&a.v(64,b)};S.prototype.v=function(a,b){this.m&a&&b!=!!(this.g&a)&&(this.b.v(this,a,b),this.g=b?this.g|a:this.g&~a)};
+var Uc=function(a,b,c){if(a.e&&a.g&b&&!c)throw Error("Component already rendered");!c&&a.g&b&&a.v(b,!1);a.m=c?a.m|b:a.m&~b},U=function(a,b){return!!(a.cc&b)&&!!(a.m&b)},T=function(a,b,c){return!!(a.m&b)&&!!(a.g&b)!=c&&(!(a.W&b)||a.dispatchEvent(fc(b,c)))&&!a.Sb};f=S.prototype;f.Qa=function(a){(!a.relatedTarget||!tb(this.a(),a.relatedTarget))&&this.dispatchEvent("enter")&&this.isEnabled()&&U(this,2)&&this.D(!0)};
+f.Pa=function(a){a.relatedTarget&&tb(this.a(),a.relatedTarget)||!this.dispatchEvent("leave")||(U(this,4)&&this.setActive(!1),U(this,2)&&this.D(!1))};f.oa=aa;f.ka=function(a){this.isEnabled()&&(U(this,2)&&this.D(!0),!Hb(a)||x&&y&&a.ctrlKey||(U(this,4)&&this.setActive(!0),this.b.J(this)&&this.k().focus()));this.xa||!Hb(a)||x&&y&&a.ctrlKey||a.preventDefault()};f.Ra=function(a){this.isEnabled()&&(U(this,2)&&this.D(!0),this.g&4&&Vc(this,a)&&U(this,4)&&this.setActive(!1))};
+f.sb=function(a){this.isEnabled()&&Vc(this,a)};var Vc=function(a,b){if(U(a,16)){var c=!(a.g&16);T(a,16,c)&&a.v(16,c)}U(a,8)&&Sc(a,!0);U(a,64)&&Tc(a,!(a.g&64));c=new D("action",a);b&&(c.altKey=b.altKey,c.ctrlKey=b.ctrlKey,c.metaKey=b.metaKey,c.shiftKey=b.shiftKey,c.bb=b.bb);return a.dispatchEvent(c)};S.prototype.ma=function(){U(this,32)&&T(this,32,!0)&&this.v(32,!0)};S.prototype.la=function(){U(this,4)&&this.setActive(!1);U(this,32)&&T(this,32,!1)&&this.v(32,!1)};
+S.prototype.K=function(a){return this.u()&&this.isEnabled()&&this.lb(a)?(a.preventDefault(),a.stopPropagation(),!0):!1};S.prototype.lb=function(a){return 13==a.keyCode&&Vc(this,a)};if(!n(S))throw Error("Invalid component class "+S);if(!n(R))throw Error("Invalid renderer class "+R);var Wc=ia(S);Fc[Wc]=R;Ec("goog-control",function(){return new S(null)});var V=function(a,b,c){N.call(this,c);this.b=b||Q.aa();this.M=a||"vertical"};p(V,N);f=V.prototype;f.ub=null;f.ga=null;f.b=null;f.M=null;f.q=!0;f.X=!0;f.Za=!0;f.j=-1;f.h=null;f.ea=!1;f.Pb=!1;f.Ob=!0;f.N=null;f.k=function(){return this.ub||this.b.k(this)};f.ya=function(){return this.ga||(this.ga=new P(this.k()))};f.zb=function(){return this.b};f.r=function(){this.c=this.b.r(this)};f.C=function(){return this.b.C(this.a())};f.Z=function(a){return this.b.Z(a)};
+f.Xa=function(a){this.c=this.b.L(this,a);"none"==a.style.display&&(this.q=!1)};f.G=function(){V.f.G.call(this);kc(this,function(a){a.e&&Xc(this,a)},this);var a=this.a();this.b.Ma(this);this.ja(this.q,!0);ic(this).d(this,"enter",this.Ib).d(this,"highlight",this.Jb).d(this,"unhighlight",this.Lb).d(this,"open",this.Kb).d(this,"close",this.Gb).d(a,"mousedown",this.ka).d(kb(a),"mouseup",this.Hb).d(a,["mousedown","mouseup","mouseover","mouseout","contextmenu"],this.Fb);this.J()&&Yc(this,!0)};
+var Yc=function(a,b){var c=ic(a),d=a.k();b?c.d(d,"focus",a.ma).d(d,"blur",a.la).d(a.ya(),"key",a.K):c.w(d,"focus",a.ma).w(d,"blur",a.la).w(a.ya(),"key",a.K)};f=V.prototype;f.da=function(){Zc(this,-1);this.h&&Tc(this.h,!1);this.ea=!1;V.f.da.call(this)};f.Ib=function(){return!0};
+f.Jb=function(a){var b=nc(this,a.target);if(-1<b&&b!=this.j){var c=O(this,this.j);c&&c.D(!1);this.j=b;c=O(this,this.j);this.ea&&c.setActive(!0);this.Ob&&this.h&&c!=this.h&&(c.m&64?Tc(c,!0):Tc(this.h,!1))}b=this.a();r(b,"The DOM element for the container cannot be null.");null!=a.target.a()&&tc(b,"activedescendant",a.target.a().id)};f.Lb=function(a){a.target==O(this,this.j)&&(this.j=-1);a=this.a();r(a,"The DOM element for the container cannot be null.");a.removeAttribute(sc("activedescendant"))};
+f.Kb=function(a){(a=a.target)&&a!=this.h&&a.getParent()==this&&(this.h&&Tc(this.h,!1),this.h=a)};f.Gb=function(a){a.target==this.h&&(this.h=null)};f.ka=function(a){this.X&&(this.ea=!0);var b=this.k();b&&xb(b)&&yb(b)?b.focus():a.preventDefault()};f.Hb=function(){this.ea=!1};
+f.Fb=function(a){var b;t:{b=a.target;if(this.N)for(var c=this.a();b&&b!==c;){var d=b.id;if(d in this.N){b=this.N[d];break t}b=b.parentNode}b=null}if(b)switch(a.type){case "mousedown":b.ka(a);break;case "mouseup":b.Ra(a);break;case "mouseover":b.Qa(a);break;case "mouseout":b.Pa(a);break;case "contextmenu":b.oa(a)}};f.ma=function(){};f.la=function(){Zc(this,-1);this.ea=!1;this.h&&Tc(this.h,!1)};
+f.K=function(a){return this.isEnabled()&&this.u()&&(0!=lc(this)||this.ub)&&this.lb(a)?(a.preventDefault(),a.stopPropagation(),!0):!1};
+f.lb=function(a){var b=O(this,this.j);if(b&&"function"==typeof b.K&&b.K(a)||this.h&&this.h!=b&&"function"==typeof this.h.K&&this.h.K(a))return!0;if(a.shiftKey||a.ctrlKey||a.metaKey||a.altKey)return!1;switch(a.keyCode){case 27:if(this.J())this.k().blur();else return!1;break;case 36:$c(this);break;case 35:ad(this);break;case 38:if("vertical"==this.M)bd(this);else return!1;break;case 37:if("horizontal"==this.M)mc(this)?cd(this):bd(this);else return!1;break;case 40:if("vertical"==this.M)cd(this);else return!1;
+break;case 39:if("horizontal"==this.M)mc(this)?bd(this):cd(this);else return!1;break;default:return!1}return!0};var Xc=function(a,b){var c=b.a(),c=c.id||(c.id=gc(b));a.N||(a.N={});a.N[c]=b};V.prototype.Ca=function(a,b){xa(a,S,"The child of a container must be a control");V.f.Ca.call(this,a,b)};V.prototype.Ta=function(a,b,c){a.W|=2;a.W|=64;!this.J()&&this.Pb||Uc(a,32,!1);a.Na(!1);V.f.Ta.call(this,a,b,c);a.e&&this.e&&Xc(this,a);b<=this.j&&this.j++};
+V.prototype.removeChild=function(a,b){if(a=m(a)?this.i&&a?(a in this.i?this.i[a]:void 0)||null:null:a){var c=nc(this,a);-1!=c&&(c==this.j?a.D(!1):c<this.j&&this.j--);var d=a.a();d&&d.id&&this.N&&(c=this.N,d=d.id,d in c&&delete c[d])}a=V.f.removeChild.call(this,a,b);a.Na(!0);return a};var Ic=function(a,b){if(a.a())throw Error("Component already rendered");a.M=b};f=V.prototype;f.u=function(){return this.q};
+f.ja=function(a,b){if(b||this.q!=a&&this.dispatchEvent(a?"show":"hide")){this.q=a;var c=this.a();c&&(M(c,a),this.J()&&Gc(this.k(),this.X&&this.q),b||this.dispatchEvent(this.q?"aftershow":"afterhide"));return!0}return!1};f.isEnabled=function(){return this.X};f.pa=function(a){this.X!=a&&this.dispatchEvent(a?"enable":"disable")&&(a?(this.X=!0,kc(this,function(a){a.vb?delete a.vb:a.pa(!0)})):(kc(this,function(a){a.isEnabled()?a.pa(!1):a.vb=!0}),this.ea=this.X=!1),this.J()&&Gc(this.k(),a&&this.q))};
+f.J=function(){return this.Za};f.na=function(a){a!=this.Za&&this.e&&Yc(this,a);this.Za=a;this.X&&this.q&&Gc(this.k(),a)};var Zc=function(a,b){var c=O(a,b);c?c.D(!0):-1<a.j&&O(a,a.j).D(!1)};V.prototype.D=function(a){Zc(this,nc(this,a))};
+var $c=function(a){dd(a,function(a,c){return(a+1)%c},lc(a)-1)},ad=function(a){dd(a,function(a,c){a--;return 0>a?c-1:a},0)},cd=function(a){dd(a,function(a,c){return(a+1)%c},a.j)},bd=function(a){dd(a,function(a,c){a--;return 0>a?c-1:a},a.j)},dd=function(a,b,c){c=0>c?nc(a,a.h):c;var d=lc(a);c=b.call(a,c,d);for(var e=0;e<=d;){var g=O(a,c);if(g&&g.u()&&g.isEnabled()&&g.m&2){a.Ua(c);break}e++;c=b.call(a,c,d)}};V.prototype.Ua=function(a){Zc(this,a)};var ed=function(){};p(ed,R);ba(ed);f=ed.prototype;f.A=function(){return"goog-tab"};f.V=function(){return"tab"};f.r=function(a){var b=ed.f.r.call(this,a);(a=a.Sa())&&this.Va(b,a);return b};f.L=function(a,b){b=ed.f.L.call(this,a,b);var c=this.Sa(b);c&&(a.rb=c);a.g&8&&(c=a.getParent())&&n(c.Y)&&(a.v(8,!1),c.Y(a));return b};f.Sa=function(a){return a.title||""};f.Va=function(a,b){a&&(a.title=b||"")};var fd=function(a,b,c){S.call(this,a,b||ed.aa(),c);Uc(this,8,!0);this.W|=9};p(fd,S);fd.prototype.Sa=function(){return this.rb};fd.prototype.Va=function(a){this.zb().Va(this.a(),a);this.rb=a};Ec("goog-tab",function(){return new fd(null)});var W=function(){};p(W,Q);ba(W);W.prototype.A=function(){return"goog-tab-bar"};W.prototype.V=function(){return"tablist"};W.prototype.ab=function(a,b,c){this.Ab||(this.Ja||gd(this),this.Ab=Ja(this.Ja));var d=this.Ab[b];d?(Ic(a,hd(d)),a.wb=d):W.f.ab.call(this,a,b,c)};W.prototype.ta=function(a){var b=W.f.ta.call(this,a);this.Ja||gd(this);b.push(this.Ja[a.wb]);return b};var gd=function(a){var b=a.A();a.Ja={top:b+"-top",bottom:b+"-bottom",start:b+"-start",end:b+"-end"}};var X=function(a,b,c){a=a||"top";Ic(this,hd(a));this.wb=a;V.call(this,this.M,b||W.aa(),c);id(this)};p(X,V);f=X.prototype;f.Zb=!0;f.I=null;f.G=function(){X.f.G.call(this);id(this)};f.removeChild=function(a,b){jd(this,a);return X.f.removeChild.call(this,a,b)};f.Ua=function(a){X.f.Ua.call(this,a);this.Zb&&this.Y(O(this,a))};f.Y=function(a){a?Sc(a,!0):this.I&&Sc(this.I,!1)};
+var jd=function(a,b){if(b&&b==a.I){for(var c=nc(a,b),d=c-1;b=O(a,d);d--)if(b.u()&&b.isEnabled()){a.Y(b);return}for(c+=1;b=O(a,c);c++)if(b.u()&&b.isEnabled()){a.Y(b);return}a.Y(null)}};f=X.prototype;f.Xb=function(a){this.I&&this.I!=a.target&&Sc(this.I,!1);this.I=a.target};f.Yb=function(a){a.target==this.I&&(this.I=null)};f.Vb=function(a){jd(this,a.target)};f.Wb=function(a){jd(this,a.target)};f.ma=function(){O(this,this.j)||this.D(this.I||O(this,0))};
+var id=function(a){ic(a).d(a,"select",a.Xb).d(a,"unselect",a.Yb).d(a,"disable",a.Vb).d(a,"hide",a.Wb)},hd=function(a){return"start"==a||"end"==a?"vertical":"horizontal"};Ec("goog-tab-bar",function(){return new X});var kd=!!l.DOMTokenList,ld=kd?function(a){return a.classList}:function(a){a=a.className;return m(a)&&a.match(/\S+/g)||[]},md=kd?function(a,b){r(!!a.classList);return a.classList.contains(b)}:function(a,b){return t(ld(a),b)},nd=kd?function(a,b){a.classList.add(b)}:function(a,b){md(a,b)||(a.className+=0<a.className.length?" "+b:b)},od=kd?function(a,b){a.classList.remove(b)}:function(a,b){md(a,b)&&(a.className=Aa(ld(a),function(a){return a!=b}).join(" "))};var Y=function(a,b,c,d,e){function g(a){a&&(a.tabIndex=0,rc(a,h.V()),nd(a,"goog-zippy-header"),pd(h,a),a&&h.Mb.d(a,"keydown",h.Nb))}L.call(this);this.n=e||lb();this.T=this.n.a(a)||null;this.Aa=this.n.a(d||null);this.fa=(this.Oa=n(b)?b:null)||!b?null:this.n.a(b);this.l=!0==c;this.Mb=new K(this);this.ob=new K(this);var h=this;g(this.T);g(this.Aa);this.U(this.l)};p(Y,L);f=Y.prototype;f.ca=!0;f.V=function(){return"tab"};f.C=function(){return this.fa};f.toggle=function(){this.U(!this.l)};
+f.U=function(a){this.fa?M(this.fa,a):a&&this.Oa&&(this.fa=this.Oa());this.fa&&nd(this.fa,"goog-zippy-content");if(this.Aa)M(this.T,!a),M(this.Aa,a);else if(this.T){var b=this.T;a?nd(b,"goog-zippy-expanded"):od(b,"goog-zippy-expanded");b=this.T;a?od(b,"goog-zippy-collapsed"):nd(b,"goog-zippy-collapsed");tc(this.T,"expanded",a)}this.l=a;this.dispatchEvent(new qd("toggle",this))};f.pb=function(){return this.ca};f.Na=function(a){this.ca!=a&&((this.ca=a)?(pd(this,this.T),pd(this,this.Aa)):this.ob.$a())};
+var pd=function(a,b){b&&a.ob.d(b,"click",a.$b)};Y.prototype.Nb=function(a){if(13==a.keyCode||32==a.keyCode)this.toggle(),this.dispatchEvent(new D("action",this)),a.preventDefault(),a.stopPropagation()};Y.prototype.$b=function(){this.toggle();this.dispatchEvent(new D("action",this))};var qd=function(a,b){D.call(this,a,b)};p(qd,D);var Z=function(a,b){this.nb=[];for(var c=nb("span","ae-zippy",mb(document,a)),d=0,e;e=c[d];d++){var g;if(void 0!=e.parentNode.parentNode.parentNode.nextElementSibling)g=e.parentNode.parentNode.parentNode.nextElementSibling;else for(g=e.parentNode.parentNode.parentNode.nextSibling;g&&1!=g.nodeType;)g=g.nextSibling;e=new Y(e,g,!1);this.nb.push(e)}this.fc=new rd(this.nb,mb(document,b))};Z.prototype.ic=function(){return this.fc};Z.prototype.jc=function(){return this.nb};
+var rd=function(a,b){this.ua=a;if(this.ua.length)for(var c=0,d;d=this.ua[c];c++)H(d,"toggle",this.Ub,!1,this);this.Ka=0;this.l=!1;c="ae-toggle ae-plus ae-action";this.ua.length||(c+=" ae-disabled");this.R=rb("span",{className:c},"Expand All");H(this.R,"click",this.Tb,!1,this);b&&b.appendChild(this.R)};rd.prototype.Tb=function(){this.ua.length&&this.U(!this.l)};
+rd.prototype.Ub=function(a){a=a.currentTarget;this.Ka=a.l?this.Ka+1:this.Ka-1;a.l!=this.l&&(a.l?(this.l=!0,sd(this,!0)):0==this.Ka&&(this.l=!1,sd(this,!1)))};rd.prototype.U=function(a){this.l=a;a=0;for(var b;b=this.ua[a];a++)b.l!=this.l&&b.U(this.l);sd(this)};
+var sd=function(a,b){(void 0!==b?b:a.l)?(ib(a.R,"ae-plus"),C(a.R,"ae-minus"),ub(a.R,"Collapse All")):(ib(a.R,"ae-minus"),C(a.R,"ae-plus"),ub(a.R,"Expand All"))},td=function(a){this.ac=a;this.Cb={};var b,c=rb("div",{},b=rb("div",{id:"ae-stats-details-tabs",className:"goog-tab-bar goog-tab-bar-top"}),rb("div",{className:"goog-tab-bar-clear"}),a=rb("div",{id:"ae-stats-details-tabs-content",className:"goog-tab-content"})),d=new X;d.L(b);H(d,"select",this.Bb,!1,this);H(d,"unselect",this.Bb,!1,this);b=
+0;for(var e;e=this.ac[b];b++)if(e=mb(document,"ae-stats-details-"+e)){var g=nb("h2",null,e)[0],h;h=g;var k=void 0;gb&&"innerText"in h?k=h.innerText.replace(/(\r\n|\r|\n)/g,"\n"):(k=[],zb(h,k,!0),k=k.join(""));k=k.replace(/ \xAD /g," ").replace(/\xAD/g,"");k=k.replace(/\u200B/g,"");gb||(k=k.replace(/ +/g," "));" "!=k&&(k=k.replace(/^\s*/,""));h=k;g&&g.parentNode&&g.parentNode.removeChild(g);g=new fd(h);this.Cb[ia(g)]=e;d.Ca(g,!0);a.appendChild(e);0==b?d.Y(g):M(e,!1)}mb(document,"bd").appendChild(c)};
+td.prototype.Bb=function(a){var b=this.Cb[ia(a.target)];M(b,"select"==a.type)};la("ae.Stats.Details.Tabs",td);la("goog.ui.Zippy",Y);Y.prototype.setExpanded=Y.prototype.U;la("ae.Stats.MakeZippys",Z);Z.prototype.getExpandCollapse=Z.prototype.ic;Z.prototype.getZippys=Z.prototype.jc;rd.prototype.setExpanded=rd.prototype.U;var $=function(){this.cb=[];this.hb=[]},ud=[[5,0.2,1],[6,0.2,1.2],[5,0.25,1.25],[6,0.25,1.5],[4,0.5,2],[5,0.5,2.5],[6,0.5,3],[4,1,4],[5,1,5],[6,1,6],[4,2,8],[5,2,10]],vd=function(a){if(0>=a)return[2,0.5,1];for(var b=1;1>a;)a*=10,b/=10;for(;10<=a;)a/=10,b*=10;for(var c=0;c<ud.length;c++)if(a<=ud[c][2])return[ud[c][0],ud[c][1]*b,ud[c][2]*b];return[5,2*b,10*b]};$.prototype.gb="stats/static/pix.gif";$.prototype.B="ae-stats-gantt-";$.prototype.fb=0;$.prototype.write=function(a){this.hb.push(a)};
+var wd=function(a,b,c,d){a.write('<tr class="'+a.B+'axisrow"><td width="20%"></td><td>');a.write('<div class="'+a.B+'axis">');for(var e=0;e<=b;e++)a.write('<img class="'+a.B+'tick" src="'+a.gb+'" alt="" '),a.write('style="left:'+e*c*d+'%"\n>'),a.write('<span class="'+a.B+'scale" style="left:'+e*c*d+'%">'),a.write("&nbsp;"+e*c+"</span>");a.write("</div></td></tr>\n")};
+$.prototype.hc=function(){this.hb=[];var a=vd(this.fb),b=a[0],c=a[1],a=100/a[2];this.write('<table class="'+this.B+'table">\n');wd(this,b,c,a);for(var d=0;d<this.cb.length;d++){var e=this.cb[d];this.write('<tr class="'+this.B+'datarow"><td width="20%">');0<e.label.length&&(0<e.ia.length&&this.write('<a class="'+this.B+'link" href="'+e.ia+'">'),this.write(e.label),0<e.ia.length&&this.write("</a>"));this.write("</td>\n<td>");this.write('<div class="'+this.B+'container">');0<e.ia.length&&this.write('<a class="'+
 this.B+'link" href="'+e.ia+'"\n>');this.write('<img class="'+this.B+'bar" src="'+this.gb+'" alt="" ');this.write('style="left:'+e.start*a+"%;width:"+e.duration*a+'%;min-width:1px"\n>');0<e.eb&&(this.write('<img class="'+this.B+'extra" src="'+this.gb+'" alt="" '),this.write('style="left:'+e.start*a+"%;width:"+e.eb*a+'%"\n>'));0<e.yb.length&&(this.write('<span class="'+this.B+'inline" style="left:'+(e.start+Math.max(e.duration,e.eb))*a+'%">&nbsp;'),this.write(e.yb),this.write("</span>"));0<e.ia.length&&
-this.write("</a>");this.write("</div></td></tr>\n")}pd(this,b,c,a);this.write("</table>\n");return this.hb.join("")};$.prototype.hc=function(a,b,c,d,e,g){this.fb=Math.max(this.fb,Math.max(b+c,b+d));this.cb.push({label:a,start:b,duration:c,eb:d,yb:e,ia:g})};ja("Gantt",$);$.prototype.add_bar=$.prototype.hc;$.prototype.draw=$.prototype.ic;})();
+this.write("</a>");this.write("</div></td></tr>\n")}wd(this,b,c,a);this.write("</table>\n");return this.hb.join("")};$.prototype.gc=function(a,b,c,d,e,g){this.fb=Math.max(this.fb,Math.max(b+c,b+d));this.cb.push({label:a,start:b,duration:c,eb:d,yb:e,ia:g})};la("Gantt",$);$.prototype.add_bar=$.prototype.gc;$.prototype.draw=$.prototype.hc;})();
diff --git a/google/appengine/ext/cloudstorage/cloudstorage_stub.py b/google/appengine/ext/cloudstorage/cloudstorage_stub.py
index 253629f..5f442e4 100644
--- a/google/appengine/ext/cloudstorage/cloudstorage_stub.py
+++ b/google/appengine/ext/cloudstorage/cloudstorage_stub.py
@@ -338,20 +338,38 @@
                  bucketpath,
                  prefix,
                  marker,
-                 max_keys):
+                 max_keys,
+                 delimiter):
     """Get bucket listing with a GET.
 
+    How GCS listbucket work in production:
+    GCS tries to return as many items as possible in a single response. If
+    there are more items satisfying user's query and the current request
+    took too long (e.g spent on skipping files in a subdir) or items to return
+    gets too big (> max_keys), it returns fast and sets IsTruncated
+    and NextMarker for continuation. They serve redundant purpose: if
+    NextMarker is set, IsTruncated is True.
+
+    Note NextMarker is not where GCS scan left off. It is
+    only valid for the exact same type of query the marker was generated from.
+    For example, if a marker is generated from query with delimiter, the marker
+    is the name of a subdir (instead of the last file within the subdir). Thus
+    you can't use this marker to issue a query without delimiter.
+
     Args:
       bucketpath: gcs bucket path of form '/bucket'
       prefix: prefix to limit listing.
-      marker: a str after which to start listing.
-      max_keys: max size of listing.
+      marker: a str after which to start listing. Exclusive.
+      max_keys: max items we scan & return.
+      delimiter: delimiter for directory.
 
     See https://developers.google.com/storage/docs/reference-methods#getbucket
     for details.
 
     Returns:
-      A list of GCSFileStat sorted by filename.
+      A tuple of (a list of GCSFileStat for files or directories sorted by
+      filename, next_marker to use as next marker, is_truncated boolean to
+      indicate if there are more results satisfying query).
     """
     common.validate_bucket_path(bucketpath)
     q = _AE_GCSFileInfo_.all(namespace='')
@@ -360,19 +378,75 @@
       q.filter('filename >', '/'.join([bucketpath, marker]))
     else:
       q.filter('filename >=', fully_qualified_prefix)
-    result = []
-    for info in q.run(limit=max_keys):
+
+    result = set()
+    name = None
+    first = True
+    first_dir = None
+    for info in q.run():
+
       if not info.filename.startswith(fully_qualified_prefix):
         break
+      if len(result) == max_keys:
+        break
+
 
       info = db.get(info.key())
-      if info:
-        result.append(common.GCSFileStat(
-            filename=info.filename,
-            st_size=info.size,
-            st_ctime=calendar.timegm(info.creation.utctimetuple()),
-            etag=info.etag))
-    return result
+      if not info:
+        continue
+
+      name = info.filename
+      if delimiter:
+
+        start_index = name.find(delimiter, len(fully_qualified_prefix))
+        if start_index != -1:
+          name = name[:start_index + len(delimiter)]
+
+
+          if marker and (first or name == first_dir):
+            first = False
+            first_dir = name
+
+          else:
+            result.add(common.GCSFileStat(name, st_size=None,
+                                          st_ctime=None, etag=None,
+                                          is_dir=True))
+          continue
+
+      first = False
+      result.add(common.GCSFileStat(
+          filename=name,
+          st_size=info.size,
+          st_ctime=calendar.timegm(info.creation.utctimetuple()),
+          etag=info.etag))
+
+    def is_truncated():
+      """Check if there are more results satisfying the query."""
+      if not result:
+        return False
+      q = _AE_GCSFileInfo_.all(namespace='')
+      q.filter('filename >', name)
+      info = None
+
+      if delimiter and name.endswith(delimiter):
+
+        for info in q.run():
+          if not info.filename.startswith(name):
+            break
+        if info.filename.startswith(name):
+          info = None
+      else:
+        info = q.get()
+      if info is None or not info.filename.startswith(fully_qualified_prefix):
+        return False
+      return True
+
+    result = list(result)
+    result.sort()
+    truncated = is_truncated()
+    next_marker = name if truncated else None
+
+    return result, next_marker, truncated
 
   @db.non_transactional
   def get_object(self, filename, start=0, end=None):
diff --git a/google/appengine/ext/cloudstorage/common.py b/google/appengine/ext/cloudstorage/common.py
index 0aa40cf..07432ff 100644
--- a/google/appengine/ext/cloudstorage/common.py
+++ b/google/appengine/ext/cloudstorage/common.py
@@ -75,6 +75,7 @@
 _GCS_BUCKET_REGEX_BASE = r'[a-z0-9\.\-_]{3,63}'
 _GCS_BUCKET_REGEX = re.compile(_GCS_BUCKET_REGEX_BASE + r'$')
 _GCS_BUCKET_PATH_REGEX = re.compile(r'/' + _GCS_BUCKET_REGEX_BASE + r'$')
+_GCS_PATH_PREFIX_REGEX = re.compile(r'/' + _GCS_BUCKET_REGEX_BASE + r'.*')
 _GCS_FULLPATH_REGEX = re.compile(r'/' + _GCS_BUCKET_REGEX_BASE + r'/.*')
 
 _GCS_METADATA = ['x-goog-meta-',
@@ -91,6 +92,10 @@
 _access_token = ''
 
 
+
+_MAX_GET_BUCKET_RESULT = 1000
+
+
 def set_access_token(access_token):
   """Set the shared access token to authenticate with Google Cloud Storage.
 
@@ -124,9 +129,13 @@
                etag,
                st_ctime,
                content_type=None,
-               metadata=None):
+               metadata=None,
+               is_dir=False):
     """Initialize.
 
+    For files, the non optional arguments are always set.
+    For directories, only filename and is_dir is set.
+
     Args:
       filename: a Google Cloud Storage filename of form '/bucket/filename'.
       st_size: file size in bytes. long compatible.
@@ -136,17 +145,27 @@
       metadata: a str->str dict of user specified options when creating
         the file. Possible keys are x-goog-meta-, content-disposition,
         content-encoding, and cache-control.
+      is_dir: True if this represents a directory. False if this is a real file.
     """
     self.filename = filename
-    self.st_size = long(st_size)
-    self.st_ctime = float(st_ctime)
-    if etag[0] == '"' and etag[-1] == '"':
-      etag = etag[1:-1]
-    self.etag = etag
+    self.is_dir = is_dir
+    self.st_size = None
+    self.st_ctime = None
+    self.etag = None
     self.content_type = content_type
     self.metadata = metadata
 
+    if not is_dir:
+      self.st_size = long(st_size)
+      self.st_ctime = float(st_ctime)
+      if etag[0] == '"' and etag[-1] == '"':
+        etag = etag[1:-1]
+      self.etag = etag
+
   def __repr__(self):
+    if self.is_dir:
+      return '(directory: %s)' % self.filename
+
     return (
         '(filename: %(filename)s, st_size: %(st_size)s, '
         'st_ctime: %(st_ctime)s, etag: %(etag)s, '
@@ -159,6 +178,22 @@
              content_type=self.content_type,
              metadata=self.metadata))
 
+  def __cmp__(self, other):
+    if not isinstance(other, self.__class__):
+      raise ValueError('Argument to cmp must have the same type. '
+                       'Expect %s, got %s', self.__class__.__name__,
+                       other.__class__.__name__)
+    if self.filename > other.filename:
+      return 1
+    elif self.filename < other.filename:
+      return -1
+    return 0
+
+  def __hash__(self):
+    if self.etag:
+      return hash(self.etag)
+    return hash(self.filename)
+
 
 
 CSFileStat = GCSFileStat
@@ -215,6 +250,32 @@
                      'but got %s' % path)
 
 
+def _process_path_prefix(path_prefix):
+  """Validate and process a Google Cloud Stoarge path prefix.
+
+  Args:
+    path_prefix: a Google Cloud Storage path prefix of format '/bucket/prefix'
+      or '/bucket/' or '/bucket'.
+
+  Raises:
+    ValueError: if path is invalid.
+
+  Returns:
+    a tuple of /bucket and prefix. prefix can be None.
+  """
+  _validate_path(path_prefix)
+  if not _GCS_PATH_PREFIX_REGEX.match(path_prefix):
+    raise ValueError('Path prefix should have format /bucket, /bucket/, '
+                     'or /bucket/prefix but got %s.' % path_prefix)
+  bucket_name_end = path_prefix.find('/', 1)
+  bucket = path_prefix
+  prefix = None
+  if bucket_name_end != -1:
+    bucket = path_prefix[:bucket_name_end]
+    prefix = path_prefix[bucket_name_end + 1:] or None
+  return bucket, prefix
+
+
 def _validate_path(path):
   """Basic validation of Google Storage paths.
 
@@ -332,9 +393,15 @@
 
 
 def local_run():
-  """Whether running in dev appserver."""
-  return ('SERVER_SOFTWARE' not in os.environ or
-          os.environ['SERVER_SOFTWARE'].startswith('Development'))
+  """Whether we should hit GCS dev appserver stub."""
+  server_software = os.environ.get('SERVER_SOFTWARE')
+  if server_software is None:
+    return True
+  if 'remote_api' in server_software:
+    return False
+  if server_software.startswith('Development'):
+    return True
+  return False
 
 
 def memory_usage(method):
@@ -347,3 +414,20 @@
                  method.__name__, runtime.memory_usage().current())
     return result
   return wrapper
+
+
+def _add_ns(tagname):
+  return '{%(ns)s}%(tag)s' % {'ns': CS_XML_NS,
+                              'tag': tagname}
+
+
+
+_T_CONTENTS = _add_ns('Contents')
+_T_LAST_MODIFIED = _add_ns('LastModified')
+_T_ETAG = _add_ns('ETag')
+_T_KEY = _add_ns('Key')
+_T_SIZE = _add_ns('Size')
+_T_PREFIX = _add_ns('Prefix')
+_T_COMMON_PREFIXES = _add_ns('CommonPrefixes')
+_T_NEXT_MARKER = _add_ns('NextMarker')
+_T_IS_TRUNCATED = _add_ns('IsTruncated')
diff --git a/google/appengine/ext/cloudstorage/stub_dispatcher.py b/google/appengine/ext/cloudstorage/stub_dispatcher.py
index 41e3e2b..1346409 100644
--- a/google/appengine/ext/cloudstorage/stub_dispatcher.py
+++ b/google/appengine/ext/cloudstorage/stub_dispatcher.py
@@ -38,8 +38,6 @@
 
 
 
-_MAX_GET_BUCKET_RESULT = 1000
-
 
 
 def _urlfetch_to_gcs_stub(url, payload, method, headers, request, response,
@@ -251,44 +249,53 @@
 def _handle_get_bucket(gcs_stub, bucketpath, param_dict):
   """Handle get bucket request."""
   prefix = _get_param('prefix', param_dict, '')
-  max_keys = _get_param('max-keys', param_dict, _MAX_GET_BUCKET_RESULT)
+  max_keys = _get_param('max-keys', param_dict, common._MAX_GET_BUCKET_RESULT)
   marker = _get_param('marker', param_dict, '')
+  delimiter = _get_param('delimiter', param_dict, '')
 
-  stats = gcs_stub.get_bucket(bucketpath,
-                              prefix,
-                              marker,
-                              max_keys)
+  stats, last_filename, is_truncated = gcs_stub.get_bucket(
+      bucketpath, prefix, marker, max_keys, delimiter)
 
   builder = ET.TreeBuilder()
   builder.start('ListBucketResult', {'xmlns': common.CS_XML_NS})
-  last_object_name = ''
   for stat in stats:
-    builder.start('Contents', {})
+    filename = stat.filename[len(bucketpath) + 1:]
+    if stat.is_dir:
+      builder.start('CommonPrefixes', {})
+      builder.start('Prefix', {})
+      builder.data(filename)
+      builder.end('Prefix')
+      builder.end('CommonPrefixes')
+    else:
+      builder.start('Contents', {})
 
-    builder.start('Key', {})
-    last_object_name = stat.filename[len(bucketpath) + 1:]
-    builder.data(last_object_name)
-    builder.end('Key')
+      builder.start('Key', {})
+      builder.data(filename)
+      builder.end('Key')
 
-    builder.start('LastModified', {})
-    builder.data(common.posix_to_dt_str(stat.st_ctime))
-    builder.end('LastModified')
+      builder.start('LastModified', {})
+      builder.data(common.posix_to_dt_str(stat.st_ctime))
+      builder.end('LastModified')
 
-    builder.start('ETag', {})
-    builder.data(stat.etag)
-    builder.end('ETag')
+      builder.start('ETag', {})
+      builder.data(stat.etag)
+      builder.end('ETag')
 
-    builder.start('Size', {})
-    builder.data(str(stat.st_size))
-    builder.end('Size')
+      builder.start('Size', {})
+      builder.data(str(stat.st_size))
+      builder.end('Size')
 
-    builder.end('Contents')
+      builder.end('Contents')
 
-  if last_object_name:
+  if last_filename:
     builder.start('NextMarker', {})
-    builder.data(last_object_name)
+    builder.data(last_filename[len(bucketpath) + 1:])
     builder.end('NextMarker')
 
+  builder.start('IsTruncated', {})
+  builder.data(str(is_truncated))
+  builder.end('IsTruncated')
+
   max_keys = _get_param('max-keys', param_dict)
   if max_keys is not None:
     builder.start('MaxKeys', {})
diff --git a/google/appengine/ext/datastore_admin/backup_handler.py b/google/appengine/ext/datastore_admin/backup_handler.py
index 2fbbb52..fd1d935 100644
--- a/google/appengine/ext/datastore_admin/backup_handler.py
+++ b/google/appengine/ext/datastore_admin/backup_handler.py
@@ -1041,17 +1041,21 @@
   if backup_info:
     complete_time = datetime.datetime.now()
     backup_info.complete_time = complete_time
+    gs_handle = None
     if backup_info.filesystem == files.GS_FILESYSTEM:
 
 
 
-      BackupInfoWriter(gs_bucket).write(backup_info)
 
-    def set_backup_info_complete_time():
+
+      gs_handle = BackupInfoWriter(gs_bucket).write(backup_info)[0]
+
+    def set_backup_info_with_finalize_info():
       backup_info = get_backup_info()
       backup_info.complete_time = complete_time
+      backup_info.gs_handle = gs_handle
       backup_info.put(force_writes=True)
-    db.run_in_transaction(set_backup_info_complete_time)
+    db.run_in_transaction(set_backup_info_with_finalize_info)
     logging.info('Backup %s completed', backup_info.name)
   else:
     logging.warn('Backup %s could not be found', backup_info_pk)
@@ -1097,6 +1101,9 @@
   def write(self, backup_info):
     """Write the metadata files for the given backup_info.
 
+    As a side effect, updates the backup_info in-memory entity object with the
+    gs_handle to the Backup info filename. This is not saved to the datastore.
+
     Args:
       backup_info: Required BackupInformation.
 
@@ -1650,7 +1657,7 @@
     if not self.kind_filter or entity.kind() in self.kind_filter:
       yield op.db.Put(entity)
       if self.app_id:
-        yield utils.ReserveKey(entity.key(), self.app_id)
+        yield utils.ReserveKey(entity.key())
 
 
 def validate_gs_bucket_name(bucket_name):
diff --git a/google/appengine/ext/datastore_admin/copy_handler.py b/google/appengine/ext/datastore_admin/copy_handler.py
index d7c38c4..fb23736 100644
--- a/google/appengine/ext/datastore_admin/copy_handler.py
+++ b/google/appengine/ext/datastore_admin/copy_handler.py
@@ -147,8 +147,8 @@
           extra_headers = dict([extra_header.split(':', 1)])
         else:
           extra_headers = None
-        target_app = remote_api_put_stub.get_remote_appid(remote_url,
-                                                          extra_headers)
+        target_app = remote_api_put_stub.get_remote_app_id(remote_url,
+                                                           extra_headers)
         op = utils.StartOperation(
             'Copying %s%s to %s' % (kinds_str, namespace_str, target_app))
         name_template = 'Copy all %(kind)s objects%(namespace)s'
@@ -237,7 +237,7 @@
     target_entity = datastore.Entity._FromPb(entity_proto)
 
     yield operation.db.Put(target_entity)
-    yield utils.ReserveKey(key, target_app)
+    yield utils.ReserveKey(target_entity.key())
     yield operation.counters.Increment(KindPathFromKey(key))
 
 
diff --git a/google/appengine/ext/datastore_admin/remote_api_put_stub.py b/google/appengine/ext/datastore_admin/remote_api_put_stub.py
index b6c715b..76bb8fe 100644
--- a/google/appengine/ext/datastore_admin/remote_api_put_stub.py
+++ b/google/appengine/ext/datastore_admin/remote_api_put_stub.py
@@ -21,8 +21,11 @@
 """An apiproxy stub that calls a remote handler via HTTP.
 
 This is a special version of the remote_api_stub which sends all traffic
-to the local backends *except* for datastore.put calls where the key
-contains a remote app_id.
+to the local backends *except* for datastore_v3 Put and datastore_v4
+AllocateIds calls where the key contains a remote app_id.
+
+Calls to datastore_v3 Put and datastore_v4 AllocateIds for which the entity
+keys contain a remote app_id are sent to the remote app.
 
 It re-implements parts of the remote_api_stub so as to replace dependencies on
 the (SDK only) appengine_rpc with urlfetch.
@@ -67,28 +70,29 @@
   """Remote Put requests do not support transactions."""
 
 
-class DatastorePutStub(object):
-  """A specialised stub for sending "puts" to a remote  App Engine datastore.
+class RemoteApiDatastoreStub(object):
+  """A specialised stub for writing to a remote App Engine datastore.
 
-  This stub passes through all requests to the normal stub except for
-  datastore put. It will check those to see if the put is for the local app
-  or a remote app, and if remote will send traffic remotely.
+  This class supports checking the app_id of a datastore op and either passing
+  the request through to the local app or sending it to a remote app.
+  Subclassed to implement supported services datastore_v3 and datastore_v4.
   """
 
-  def __init__(self, remote_url, target_appid, extra_headers, normal_stub):
+
+  _SERVICE_NAME = None
+
+  def __init__(self, remote_url, target_app_id, extra_headers, normal_stub):
     """Constructor.
 
     Args:
       remote_url: The URL of the remote_api handler.
-      target_appid: The appid to intercept calls for.
+      target_app_id: The app_id to intercept calls for.
       extra_headers: Headers to send (for authentication).
       normal_stub: The standard stub to delegate most calls to.
     """
     self.remote_url = remote_url
-    self.target_appid = target_appid
-    self.extra_headers = extra_headers or {}
-    if 'X-appcfg-api-version' not in self.extra_headers:
-      self.extra_headers['X-appcfg-api-version'] = '1'
+    self.target_app_id = target_app_id
+    self.extra_headers = InsertDefaultExtraHeaders(extra_headers)
     self.normal_stub = normal_stub
 
   def CreateRPC(self):
@@ -99,9 +103,15 @@
     """
     return apiproxy_rpc.RPC(stub=self)
 
+  @classmethod
+  def ServiceName(cls):
+    """Return the name of the datastore service supported by this stub."""
+    return cls._SERVICE_NAME
+
   def MakeSyncCall(self, service, call, request, response):
     """Handle all calls to this stub; delegate as appropriate."""
-    assert service == 'datastore_v3'
+    assert service == self.ServiceName(), '%s does not support service %s' % (
+        type(self), service)
 
     explanation = []
     assert request.IsInitialized(explanation), explanation
@@ -153,6 +163,17 @@
     else:
       response.ParseFromString(response_pb.response())
 
+
+class RemoteApiDatastoreV3Stub(RemoteApiDatastoreStub):
+  """A specialised stub for calling datastore_v3 Put on a foreign datastore.
+
+  This stub passes through all requests to the normal stub except for
+  datastore v3 Put. It will check those to see if the put is for the local app
+  or a remote app, and if remote will send traffic remotely.
+  """
+
+  _SERVICE_NAME = 'datastore_v3'
+
   def _Dynamic_Put(self, request, response):
     """Handle a Put request and route remotely if it matches the target app.
 
@@ -166,36 +187,54 @@
 
     if request.entity_list():
       entity = request.entity(0)
-      if entity.has_key() and entity.key().app() == self.target_appid:
+      if entity.has_key() and entity.key().app() == self.target_app_id:
         if request.has_transaction():
 
 
           raise RemoteTransactionsUnimplemented()
-        self._MakeRemoteSyncCall('datastore_v3', 'Put', request, response)
+        self._MakeRemoteSyncCall(self.ServiceName(), 'Put', request, response)
         return
 
 
-    self.normal_stub.MakeSyncCall('datastore_v3', 'Put', request, response)
+    self.normal_stub.MakeSyncCall(self.ServiceName(), 'Put', request, response)
 
 
+class RemoteApiDatastoreV4Stub(RemoteApiDatastoreStub):
+  """A remote api stub to call datastore_v4 AllocateIds on a foreign datastore.
 
+  This stub passes through all requests to the normal datastore_v4 stub except
+  for datastore v4 AllocateIds. It will check those to see if the keys are for
+  the local app or a remote app, and if remote will send traffic remotely.
+  """
+
+  _SERVICE_NAME = 'datastore_v4'
 
   def _Dynamic_AllocateIds(self, request, response):
-    """Handle AllocateIds and route remotely if it matches the target app.
+    """Handle v4 AllocateIds and route remotely if it matches the target app.
 
     Args:
-      request: A datastore_pb.AllocateIdsRequest
-      response: A datastore_pb.AllocateIdsResponse
+      request: A datastore_v4_pb.AllocateIdsRequest
+      response: A datastore_v4_pb.AllocateIdsResponse
     """
-    if request.model_key().app() == self.target_appid:
-      self._MakeRemoteSyncCall('datastore_v3', 'AllocateIds', request, response)
+
+    if request.reserve_size() > 0:
+      app_id = request.reserve(0).partition_id().dataset_id()
+    elif request.allocate_size() > 0:
+      app_id = request.allocate(0).partition_id().dataset_id()
     else:
-      self.normal_stub.MakeSyncCall('datastore_v3', 'AllocateIds', request,
+      app_id = None
+
+    if app_id == self.target_app_id:
+      self._MakeRemoteSyncCall(self.ServiceName(), 'AllocateIds', request,
+                               response)
+    else:
+      self.normal_stub.MakeSyncCall(self.ServiceName(), 'AllocateIds', request,
                                     response)
 
 
-def get_remote_appid(remote_url, extra_headers=None):
-  """Get the appid from the remote_api endpoint.
+
+def get_remote_app_id(remote_url, extra_headers=None):
+  """Get the app_id from the remote_api endpoint.
 
   This also has the side effect of verifying that it is a remote_api endpoint.
 
@@ -249,27 +288,72 @@
   return app_info['app_id']
 
 
-def configure_remote_put(remote_url, app_id, extra_headers=None):
-  """Does necessary setup to intercept PUT.
+def InsertDefaultExtraHeaders(extra_headers):
+  """Add defaults to extra_headers arg for stub configuration.
+
+  This permits comparison of a proposed RemoteApiDatastoreStub config with
+  an existing config.
+
+  Args:
+    extra_headers: The dict of headers to transform.
+
+  Returns:
+    A new copy of the input dict with defaults set.
+  """
+  extra_headers = extra_headers.copy() if extra_headers else {}
+  if 'X-appcfg-api-version' not in extra_headers:
+    extra_headers['X-appcfg-api-version'] = '1'
+  return extra_headers
+
+
+def StubConfigEqualsRequestedConfig(stub, remote_url, target_app_id,
+                                    extra_headers):
+  """Return true if the stub and requseted stub config match.
+
+  Args:
+    stub: a RemoteApiDatastore stub.
+    remote_url: requested remote_api url of target app.
+    target_app_id: requested app_id of target (remote) app.
+    extra_headers: requested headers for auth, possibly not yet including
+      defaults applied at stub instantiation time.
+
+  Returns:
+    True if the requested config matches the stub, else False.
+  """
+  return (stub.remote_url == remote_url and
+          stub.target_app_id == target_app_id and
+          stub.extra_headers == InsertDefaultExtraHeaders(extra_headers))
+
+
+def configure_remote_put(remote_url, target_app_id, extra_headers=None):
+  """Does necessary setup to intercept v3 Put and v4 AllocateIds.
 
   Args:
     remote_url: The url to the remote_api handler.
-    app_id: The app_id of the target app.
+    target_app_id: The app_id of the target app.
     extra_headers: Headers to send (for authentication).
 
   Raises:
-    ConfigurationError: if there is a error configuring the stub.
+    ConfigurationError: if there is a error configuring the stubs.
   """
-  if not app_id or not remote_url:
+  if not target_app_id or not remote_url:
     raise ConfigurationError('app_id and remote_url required')
 
-
-
-  original_datastore_stub = apiproxy_stub_map.apiproxy.GetStub('datastore_v3')
-  if isinstance(original_datastore_stub, DatastorePutStub):
-
-    logging.info('Stub is already configured. Hopefully in a matching fashion.')
-    return
-  datastore_stub = DatastorePutStub(remote_url, app_id, extra_headers,
-                                    original_datastore_stub)
-  apiproxy_stub_map.apiproxy.RegisterStub('datastore_v3', datastore_stub)
+  for stub_class in (RemoteApiDatastoreV3Stub, RemoteApiDatastoreV4Stub):
+    service_name = stub_class.ServiceName()
+    original_datastore_stub = apiproxy_stub_map.apiproxy.GetStub(service_name)
+    if isinstance(original_datastore_stub, stub_class):
+      logging.info('Datastore Admin %s RemoteApi stub is already configured.',
+                   service_name)
+      if not StubConfigEqualsRequestedConfig(
+          original_datastore_stub, remote_url, target_app_id, extra_headers):
+        logging.warning('Requested Datastore Admin %s RemoteApi stub '
+                        'configuration differs from existing configuration, '
+                        'attempting reconfiguration.', service_name)
+        datastore_stub = stub_class(remote_url, target_app_id, extra_headers,
+                                    original_datastore_stub.normal_stub)
+        apiproxy_stub_map.apiproxy.ReplaceStub(service_name, datastore_stub)
+    else:
+      datastore_stub = stub_class(remote_url, target_app_id, extra_headers,
+                                  original_datastore_stub)
+      apiproxy_stub_map.apiproxy.RegisterStub(service_name, datastore_stub)
diff --git a/google/appengine/ext/datastore_admin/static/js/compiled.js b/google/appengine/ext/datastore_admin/static/js/compiled.js
index e4d8333..a5d91dd 100644
--- a/google/appengine/ext/datastore_admin/static/js/compiled.js
+++ b/google/appengine/ext/datastore_admin/static/js/compiled.js
@@ -1,19 +1,19 @@
 var g=document,k=Array,l=Error,m=parseInt,p=String;function aa(a,b){return a.currentTarget=b}function ba(a,b){return a.keyCode=b}function q(a,b){return a.disabled=b}
-var r="push",s="shift",t="slice",u="replace",v="value",ca="preventDefault",w="indexOf",x="keyCode",A="type",da="name",B="length",ea="propertyIsEnumerable",C="prototype",fa="checked",D="split",E="style",ga="target",F="call",ha="apply",ia="navigator",G,H=this,I=function(a){var b=typeof a;if("object"==b)if(a){if(a instanceof k)return"array";if(a instanceof Object)return b;var c=Object[C].toString[F](a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a[B]&&"undefined"!=
-typeof a.splice&&"undefined"!=typeof a[ea]&&!a[ea]("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a[F]&&"undefined"!=typeof a[ea]&&!a[ea]("call"))return"function"}else return"null";else if("function"==b&&"undefined"==typeof a[F])return"object";return b},ja=function(a){var b=I(a);return"array"==b||"object"==b&&"number"==typeof a[B]},J=function(a){return"string"==typeof a},ka=function(a){var b=typeof a;return"object"==b&&null!=a||"function"==b},na=function(a){return a[la]||(a[la]=
-++ma)},la="closure_uid_"+(1E9*Math.random()>>>0),ma=0,oa=function(a,b){var c=k[C][t][F](arguments,1);return function(){var b=c[t]();b[r][ha](b,arguments);return a[ha](this,b)}},pa=function(a,b){function c(){}c.prototype=b[C];a.t=b[C];a.prototype=new c};var K=function(a){l.captureStackTrace?l.captureStackTrace(this,K):this.stack=l().stack||"";a&&(this.message=p(a))};pa(K,l);K[C].name="CustomError";var qa=function(a,b){for(var c=a[D]("%s"),d="",f=k[C][t][F](arguments,1);f[B]&&1<c[B];)d+=c[s]()+f[s]();return d+c.join("%s")},wa=function(a,b){if(b)return a[u](ra,"&amp;")[u](sa,"&lt;")[u](ta,"&gt;")[u](ua,"&quot;");if(!va.test(a))return a;-1!=a[w]("&")&&(a=a[u](ra,"&amp;"));-1!=a[w]("<")&&(a=a[u](sa,"&lt;"));-1!=a[w](">")&&(a=a[u](ta,"&gt;"));-1!=a[w]('"')&&(a=a[u](ua,"&quot;"));return a},ra=/&/g,sa=/</g,ta=/>/g,ua=/\"/g,va=/[&<>\"]/,xa=function(a){return p(a)[u](/\-([a-z])/g,function(a,c){return c.toUpperCase()})},
-ya=function(a,b){var c=J(b)?p(b)[u](/([-()\[\]{}+?*.$\^|,:#<!\\])/g,"\\$1")[u](/\x08/g,"\\x08"):"\\s";return a[u](RegExp("(^"+(c?"|["+c+"]+":"")+")([a-z])","g"),function(a,b,c){return b+c.toUpperCase()})};var za=function(a,b){b.unshift(a);K[F](this,qa[ha](null,b));b[s]()};pa(za,K);za[C].name="AssertionError";var L=function(a,b,c){if(!a){var d=k[C][t][F](arguments,2),f="Assertion failed";if(b)var f=f+(": "+b),e=d;throw new za(""+f,e||[]);}return a};var M=k[C],Aa=M[w]?function(a,b,c){L(null!=a[B]);return M[w][F](a,b,c)}:function(a,b,c){c=null==c?0:0>c?Math.max(0,a[B]+c):c;if(J(a))return J(b)&&1==b[B]?a[w](b,c):-1;for(;c<a[B];c++)if(c in a&&a[c]===b)return c;return-1},Ba=M.forEach?function(a,b,c){L(null!=a[B]);M.forEach[F](a,b,c)}:function(a,b,c){for(var d=a[B],f=J(a)?a[D](""):a,e=0;e<d;e++)e in f&&b[F](c,f[e],e,a)},Ca=function(a){var b=a[B];if(0<b){for(var c=k(b),d=0;d<b;d++)c[d]=a[d];return c}return[]},Da=function(a,b,c){L(null!=a[B]);return 2>=
-arguments[B]?M[t][F](a,b):M[t][F](a,b,c)};var Ea=function(a,b,c){for(var d in a)b[F](c,a[d],d,a)},Fa="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),Ga=function(a,b){for(var c,d,f=1;f<arguments[B];f++){d=arguments[f];for(c in d)a[c]=d[c];for(var e=0;e<Fa[B];e++)c=Fa[e],Object[C].hasOwnProperty[F](d,c)&&(a[c]=d[c])}};var N,Ha,Ia,Ja,Ka=function(){return H[ia]?H[ia].userAgent:null};Ja=Ia=Ha=N=!1;var O;if(O=Ka()){var La=H[ia];N=0==O.lastIndexOf("Opera",0);Ha=!N&&(-1!=O[w]("MSIE")||-1!=O[w]("Trident"));Ia=!N&&-1!=O[w]("WebKit");Ja=!N&&!Ia&&!Ha&&"Gecko"==La.product}var Ma=N,P=Ha,Q=Ja,R=Ia,Na=H[ia],Oa=-1!=(Na&&Na.platform||"")[w]("Mac"),Pa=function(){var a=H.document;return a?a.documentMode:void 0},Qa;
-t:{var Ra="",S;if(Ma&&H.opera)var Sa=H.opera.version,Ra="function"==typeof Sa?Sa():Sa;else if(Q?S=/rv\:([^\);]+)(\)|;)/:P?S=/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/:R&&(S=/WebKit\/(\S+)/),S)var Ta=S.exec(Ka()),Ra=Ta?Ta[1]:"";if(P){var Ua=Pa();if(Ua>parseFloat(Ra)){Qa=p(Ua);break t}}Qa=Ra}
-var Va=Qa,Wa={},T=function(a){var b;if(!(b=Wa[a])){b=0;for(var c=p(Va)[u](/^[\s\xa0]+|[\s\xa0]+$/g,"")[D]("."),d=p(a)[u](/^[\s\xa0]+|[\s\xa0]+$/g,"")[D]("."),f=Math.max(c[B],d[B]),e=0;0==b&&e<f;e++){var h=c[e]||"",n=d[e]||"",ob=RegExp("(\\d*)(\\D*)","g"),pb=RegExp("(\\d*)(\\D*)","g");do{var y=ob.exec(h)||["","",""],z=pb.exec(n)||["","",""];if(0==y[0][B]&&0==z[0][B])break;b=((0==y[1][B]?0:m(y[1],10))<(0==z[1][B]?0:m(z[1],10))?-1:(0==y[1][B]?0:m(y[1],10))>(0==z[1][B]?0:m(z[1],10))?1:0)||((0==y[2][B])<
-(0==z[2][B])?-1:(0==y[2][B])>(0==z[2][B])?1:0)||(y[2]<z[2]?-1:y[2]>z[2]?1:0)}while(0==b)}b=Wa[a]=0<=b}return b},Xa=H.document,Ya=Xa&&P?Pa()||("CSS1Compat"==Xa.compatMode?m(Va,10):5):void 0;var Za=!P||P&&9<=Ya;!Q&&!P||P&&P&&9<=Ya||Q&&T("1.9.1");P&&T("9");var $a=function(a,b){var c;c=a.className;c=J(c)&&c.match(/\S+/g)||[];for(var d=Da(arguments,1),f=c[B]+d[B],e=c,h=0;h<d[B];h++)0<=Aa(e,d[h])||e[r](d[h]);a.className=c.join(" ");return c[B]==f};var U=function(a,b){return J(b)?a.getElementById(b):b},ab=function(a,b,c,d){a=d||a;b=b&&"*"!=b?b.toUpperCase():"";if(a.querySelectorAll&&a.querySelector&&(b||c))return a.querySelectorAll(b+(c?"."+c:""));if(c&&a.getElementsByClassName){a=a.getElementsByClassName(c);if(b){d={};for(var f=0,e=0,h;h=a[e];e++)b==h.nodeName&&(d[f++]=h);d.length=f;return d}return a}a=a.getElementsByTagName(b||"*");if(c){d={};for(e=f=0;h=a[e];e++)b=h.className,"function"==typeof b[D]&&0<=Aa(b[D](/\s+/),c)&&(d[f++]=h);d.length=
-f;return d}return a},cb=function(a,b){Ea(b,function(b,d){"style"==d?a[E].cssText=b:"class"==d?a.className=b:"for"==d?a.htmlFor=b:d in bb?a.setAttribute(bb[d],b):0==d.lastIndexOf("aria-",0)||0==d.lastIndexOf("data-",0)?a.setAttribute(d,b):a[d]=b})},bb={cellpadding:"cellPadding",cellspacing:"cellSpacing",colspan:"colSpan",frameborder:"frameBorder",height:"height",maxlength:"maxLength",role:"role",rowspan:"rowSpan",type:"type",usemap:"useMap",valign:"vAlign",width:"width"},eb=function(a,b,c){var d=arguments,
-f=d[0],e=d[1];if(!Za&&e&&(e[da]||e[A])){f=["<",f];e[da]&&f[r](' name="',wa(e[da]),'"');if(e[A]){f[r](' type="',wa(e[A]),'"');var h={};Ga(h,e);delete h[A];e=h}f[r](">");f=f.join("")}f=g.createElement(f);e&&(J(e)?f.className=e:"array"==I(e)?$a[ha](null,[f].concat(e)):cb(f,e));2<d[B]&&db(g,f,d,2);return f},db=function(a,b,c,d){function f(c){c&&b.appendChild(J(c)?a.createTextNode(c):c)}for(;d<c[B];d++){var e=c[d];if(!ja(e)||ka(e)&&0<e.nodeType)f(e);else{var h;t:{if(e&&"number"==typeof e[B]){if(ka(e)){h=
-"function"==typeof e.item||"string"==typeof e.item;break t}if("function"==I(e)){h="function"==typeof e.item;break t}}h=!1}Ba(h?Ca(e):e,f)}}};var fb=function(a){var b=a[A];if(void 0===b)return null;switch(b.toLowerCase()){case "checkbox":case "radio":return a[fa]?a[v]:null;case "select-one":return b=a.selectedIndex,0<=b?a.options[b][v]:null;case "select-multiple":for(var b=[],c,d=0;c=a.options[d];d++)c.selected&&b[r](c[v]);return b[B]?b:null;default:return void 0!==a[v]?a[v]:null}};var gb=function(a){gb[" "](a);return a};gb[" "]=function(){};var hb=!P||P&&9<=Ya,ib=P&&!T("9");!R||T("528");Q&&T("1.9b")||P&&T("8")||Ma&&T("9.5")||R&&T("528");Q&&!T("8")||P&&T("9");var V=function(a,b){this.type=a;this.target=b;aa(this,this[ga])};V[C].m=!1;V[C].defaultPrevented=!1;V[C].v=!0;V[C].preventDefault=function(){this.defaultPrevented=!0;this.v=!1};var W=function(a,b){a&&this.u(a,b)};pa(W,V);G=W[C];G.target=null;G.relatedTarget=null;G.offsetX=0;G.offsetY=0;G.clientX=0;G.clientY=0;G.screenX=0;G.screenY=0;G.button=0;ba(G,0);G.charCode=0;G.ctrlKey=!1;G.altKey=!1;G.shiftKey=!1;G.metaKey=!1;G.s=!1;G.o=null;
-G.u=function(a,b){var c=this.type=a[A];V[F](this,c);this.target=a[ga]||a.srcElement;aa(this,b);var d=a.relatedTarget;if(d){if(Q){var f;t:{try{gb(d.nodeName);f=!0;break t}catch(e){}f=!1}f||(d=null)}}else"mouseover"==c?d=a.fromElement:"mouseout"==c&&(d=a.toElement);this.relatedTarget=d;this.offsetX=R||void 0!==a.offsetX?a.offsetX:a.layerX;this.offsetY=R||void 0!==a.offsetY?a.offsetY:a.layerY;this.clientX=void 0!==a.clientX?a.clientX:a.pageX;this.clientY=void 0!==a.clientY?a.clientY:a.pageY;this.screenX=
-a.screenX||0;this.screenY=a.screenY||0;this.button=a.button;ba(this,a[x]||0);this.charCode=a.charCode||("keypress"==c?a[x]:0);this.ctrlKey=a.ctrlKey;this.altKey=a.altKey;this.shiftKey=a.shiftKey;this.metaKey=a.metaKey;this.s=Oa?a.metaKey:a.ctrlKey;this.state=a.state;this.o=a;a.defaultPrevented&&this[ca]();delete this.m};G.preventDefault=function(){W.t[ca][F](this);var a=this.o;if(a[ca])a[ca]();else if(a.returnValue=!1,ib)try{(a.ctrlKey||112<=a[x]&&123>=a[x])&&ba(a,-1)}catch(b){}};var jb="closure_listenable_"+(1E6*Math.random()|0),kb=0;var lb=function(a,b,c,d,f,e){this.c=a;this.g=b;this.src=c;this.type=d;this.capture=!!f;this.j=e;this.key=++kb;this.e=this.k=!1};lb[C].n=function(){this.e=!0;this.j=this.src=this.g=this.c=null};var mb=function(a){this.src=a;this.a={};this.l=0};mb[C].add=function(a,b,c,d,f){var e=this.a[a];e||(e=this.a[a]=[],this.l++);var h;t:{for(h=0;h<e[B];++h){var n=e[h];if(!n.e&&n.c==b&&n.capture==!!d&&n.j==f)break t}h=-1}-1<h?(a=e[h],c||(a.k=!1)):(a=new lb(b,null,this.src,a,!!d,f),a.k=c,e[r](a));return a};mb[C].p=function(a){var b=a[A];if(!(b in this.a))return!1;var c=this.a[b],d=Aa(c,a),f;if(f=0<=d)L(null!=c[B]),M.splice[F](c,d,1);f&&(a.n(),0==this.a[b][B]&&(delete this.a[b],this.l--));return f};var nb={},X={},Y={},qb=function(a,b,c,d,f){if("array"==I(b)){for(var e=0;e<b[B];e++)qb(a,b[e],c,d,f);return null}c=rb(c);if(a&&a[jb])a=a.A(b,c,d,f);else{e=c;if(!b)throw l("Invalid event type");c=!!d;var h=na(a),n=X[h];n||(X[h]=n=new mb(a));d=n.add(b,e,!1,d,f);d.g||(f=sb(),d.g=f,f.src=a,f.c=d,a.addEventListener?a.addEventListener(b,f,c):a.attachEvent(b in Y?Y[b]:Y[b]="on"+b,f),nb[d.key]=d);a=d}return a},sb=function(){var a=tb,b=hb?function(c){return a[F](b.src,b.c,c)}:function(c){c=a[F](b.src,b.c,
-c);if(!c)return c};return b},vb=function(a,b,c,d){var f=1;if(a=a[la]?X[na(a)]||null:null)if(b=a.a[b])for(b=Ca(b),a=0;a<b[B];a++){var e=b[a];e&&(e.capture==c&&!e.e)&&(f&=!1!==ub(e,d))}return Boolean(f)},ub=function(a,b){var c=a.c,d=a.j||a.src;if(a.k&&"number"!=typeof a&&a&&!a.e){var f=a.src;if(f&&f[jb])f.w(a);else{var e=a[A],h=a.g;f.removeEventListener?f.removeEventListener(e,h,a.capture):f.detachEvent&&f.detachEvent(e in Y?Y[e]:Y[e]="on"+e,h);(e=f[la]?X[na(f)]||null:null)?(e.p(a),0==e.l&&(e.src=null,
-delete X[na(f)])):a.n();delete nb[a.key]}}return c[F](d,b)},tb=function(a,b){if(a.e)return!0;if(!hb){var c;if(!(c=b))t:{c=["window","event"];for(var d=H,f;f=c[s]();)if(null!=d[f])d=d[f];else{c=null;break t}c=d}f=c;c=new W(f,this);d=!0;if(!(0>f[x]||void 0!=f.returnValue)){t:{var e=!1;if(0==f[x])try{ba(f,-1);break t}catch(h){e=!0}if(e||void 0==f.returnValue)f.returnValue=!0}f=[];for(e=c.currentTarget;e;e=e.parentNode)f[r](e);for(var e=a[A],n=f[B]-1;!c.m&&0<=n;n--)aa(c,f[n]),d&=vb(f[n],e,!0,c);for(n=
-0;!c.m&&n<f[B];n++)aa(c,f[n]),d&=vb(f[n],e,!1,c)}return d}return ub(a,new W(b,this))},wb="__closure_events_fn_"+(1E9*Math.random()>>>0),rb=function(a){L(a,"Listener can not be null.");if("function"==I(a))return a;L(a.handleEvent,"An object listener must have handleEvent method.");return a[wb]||(a[wb]=function(b){return a.handleEvent(b)})};var xb=function(a,b,c){var d;t:if(d=xa(c),void 0===a[E][d]&&(c=(R?"Webkit":Q?"Moz":P?"ms":Ma?"O":null)+ya(c),void 0!==a[E][c])){d=c;break t}d&&(a[E][d]=b)};var yb=function(a,b){var c=[];1<arguments[B]&&(c=k[C][t][F](arguments)[t](1));var d=ab(g,"th","tct-selectall",a);if(0!=d[B]){var d=d[0],f=0,e=ab(g,"tbody",null,a);e[B]&&(f=e[0].rows[B]);this.d=eb("input",{type:"checkbox"});d.appendChild(this.d);f?qb(this.d,"click",this.r,!1,this):q(this.d,!0);this.f=[];this.h=[];this.i=[];d=ab(g,"input",null,a);for(f=0;e=d[f];f++)"checkbox"==e[A]&&e!=this.d?(this.f[r](e),qb(e,"click",this.q,!1,this)):"action"==e[da]&&(0<=c[w](e[v])?this.i[r](e):this.h[r](e),q(e,!0))}};
-G=yb[C];G.f=null;G.b=0;G.d=null;G.h=null;G.i=null;G.r=function(a){for(var b=a[ga][fa],c=a=0,d;d=this.f[c];c++)d.checked=b,a+=1;this.b=b?this.f[B]:0;for(c=0;b=this.h[c];c++)q(b,!this.b);for(c=0;b=this.i[c];c++)q(b,1!=a?!0:!1)};G.q=function(a){this.b+=a[ga][fa]?1:-1;this.d.checked=this.b==this.f[B];a=0;for(var b;b=this.h[a];a++)q(b,!this.b);for(a=0;b=this.i[a];a++)q(b,1!=this.b?!0:!1)};var zb=function(){var a=U(g,"kinds");a&&new yb(a);(a=U(g,"pending_backups"))&&new yb(a);(a=U(g,"backups"))&&new yb(a,"Restore");var b=U(g,"ae-datastore-admin-filesystem");b&&qb(b,"change",function(){var a="gs"==fb(b);U(g,"gs_bucket_tr")[E].display=a?"":"none"});if(a=U(g,"confirm_delete_form")){var c=U(g,"confirm_readonly_delete");c&&(a.onsubmit=function(){var a=U(g,"confirm_message");J("color")?xb(a,"red","color"):Ea("color",oa(xb,a));return c[fa]})}},Z=["ae","Datastore","Admin","init"],$=H;
-Z[0]in $||!$.execScript||$.execScript("var "+Z[0]);for(var Ab;Z[B]&&(Ab=Z[s]());)Z[B]||void 0===zb?$=$[Ab]?$[Ab]:$[Ab]={}:$[Ab]=zb;
+var r="push",s="shift",t="slice",u="replace",v="value",ca="preventDefault",w="indexOf",x="keyCode",A="type",da="name",B="length",ea="propertyIsEnumerable",C="prototype",fa="checked",D="split",E="style",ga="target",F="call",ha="apply",G,H=this,I=function(a){var b=typeof a;if("object"==b)if(a){if(a instanceof k)return"array";if(a instanceof Object)return b;var c=Object[C].toString[F](a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a[B]&&"undefined"!=typeof a.splice&&
+"undefined"!=typeof a[ea]&&!a[ea]("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a[F]&&"undefined"!=typeof a[ea]&&!a[ea]("call"))return"function"}else return"null";else if("function"==b&&"undefined"==typeof a[F])return"object";return b},ia=function(a){var b=I(a);return"array"==b||"object"==b&&"number"==typeof a[B]},J=function(a){return"string"==typeof a},ja=function(a){var b=typeof a;return"object"==b&&null!=a||"function"==b},ka=function(a,b){var c=k[C][t][F](arguments,1);
+return function(){var b=c[t]();b[r][ha](b,arguments);return a[ha](this,b)}},la=function(a,b){function c(){}c.prototype=b[C];a.s=b[C];a.prototype=new c};var K=function(a){l.captureStackTrace?l.captureStackTrace(this,K):this.stack=l().stack||"";a&&(this.message=p(a))};la(K,l);K[C].name="CustomError";var ma=function(a,b){for(var c=a[D]("%s"),d="",f=k[C][t][F](arguments,1);f[B]&&1<c[B];)d+=c[s]()+f[s]();return d+c.join("%s")},sa=function(a,b){if(b)return a[u](na,"&amp;")[u](oa,"&lt;")[u](pa,"&gt;")[u](qa,"&quot;");if(!ra.test(a))return a;-1!=a[w]("&")&&(a=a[u](na,"&amp;"));-1!=a[w]("<")&&(a=a[u](oa,"&lt;"));-1!=a[w](">")&&(a=a[u](pa,"&gt;"));-1!=a[w]('"')&&(a=a[u](qa,"&quot;"));return a},na=/&/g,oa=/</g,pa=/>/g,qa=/\"/g,ra=/[&<>\"]/,ta=function(a){return p(a)[u](/\-([a-z])/g,function(a,c){return c.toUpperCase()})},
+ua=function(a,b){var c=J(b)?p(b)[u](/([-()\[\]{}+?*.$\^|,:#<!\\])/g,"\\$1")[u](/\x08/g,"\\x08"):"\\s";return a[u](RegExp("(^"+(c?"|["+c+"]+":"")+")([a-z])","g"),function(a,b,c){return b+c.toUpperCase()})};var va=function(a,b){b.unshift(a);K[F](this,ma[ha](null,b));b[s]()};la(va,K);va[C].name="AssertionError";var L=function(a,b,c){if(!a){var d=k[C][t][F](arguments,2),f="Assertion failed";if(b)var f=f+(": "+b),e=d;throw new va(""+f,e||[]);}return a};var M=k[C],wa=M[w]?function(a,b,c){L(null!=a[B]);return M[w][F](a,b,c)}:function(a,b,c){c=null==c?0:0>c?Math.max(0,a[B]+c):c;if(J(a))return J(b)&&1==b[B]?a[w](b,c):-1;for(;c<a[B];c++)if(c in a&&a[c]===b)return c;return-1},xa=M.forEach?function(a,b,c){L(null!=a[B]);M.forEach[F](a,b,c)}:function(a,b,c){for(var d=a[B],f=J(a)?a[D](""):a,e=0;e<d;e++)e in f&&b[F](c,f[e],e,a)},ya=function(a){var b=a[B];if(0<b){for(var c=k(b),d=0;d<b;d++)c[d]=a[d];return c}return[]},za=function(a,b,c){L(null!=a[B]);return 2>=
+arguments[B]?M[t][F](a,b):M[t][F](a,b,c)};var Aa=function(a,b,c){for(var d in a)b[F](c,a[d],d,a)},Ba="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),Ca=function(a,b){for(var c,d,f=1;f<arguments[B];f++){d=arguments[f];for(c in d)a[c]=d[c];for(var e=0;e<Ba[B];e++)c=Ba[e],Object[C].hasOwnProperty[F](d,c)&&(a[c]=d[c])}};var N,Da,Ea,Fa,Ga=function(){return H.navigator?H.navigator.userAgent:null};Fa=Ea=Da=N=!1;var O;if(O=Ga()){var Ha=H.navigator;N=0==O.lastIndexOf("Opera",0);Da=!N&&(-1!=O[w]("MSIE")||-1!=O[w]("Trident"));Ea=!N&&-1!=O[w]("WebKit");Fa=!N&&!Ea&&!Da&&"Gecko"==Ha.product}var Ia=N,P=Da,Q=Fa,R=Ea,Ja=function(){var a=H.document;return a?a.documentMode:void 0},Ka;
+t:{var La="",S;if(Ia&&H.opera)var Ma=H.opera.version,La="function"==typeof Ma?Ma():Ma;else if(Q?S=/rv\:([^\);]+)(\)|;)/:P?S=/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/:R&&(S=/WebKit\/(\S+)/),S)var Na=S.exec(Ga()),La=Na?Na[1]:"";if(P){var Oa=Ja();if(Oa>parseFloat(La)){Ka=p(Oa);break t}}Ka=La}
+var Pa=Ka,Qa={},T=function(a){var b;if(!(b=Qa[a])){b=0;for(var c=p(Pa)[u](/^[\s\xa0]+|[\s\xa0]+$/g,"")[D]("."),d=p(a)[u](/^[\s\xa0]+|[\s\xa0]+$/g,"")[D]("."),f=Math.max(c[B],d[B]),e=0;0==b&&e<f;e++){var h=c[e]||"",n=d[e]||"",jb=RegExp("(\\d*)(\\D*)","g"),kb=RegExp("(\\d*)(\\D*)","g");do{var y=jb.exec(h)||["","",""],z=kb.exec(n)||["","",""];if(0==y[0][B]&&0==z[0][B])break;b=((0==y[1][B]?0:m(y[1],10))<(0==z[1][B]?0:m(z[1],10))?-1:(0==y[1][B]?0:m(y[1],10))>(0==z[1][B]?0:m(z[1],10))?1:0)||((0==y[2][B])<
+(0==z[2][B])?-1:(0==y[2][B])>(0==z[2][B])?1:0)||(y[2]<z[2]?-1:y[2]>z[2]?1:0)}while(0==b)}b=Qa[a]=0<=b}return b},Ra=H.document,Sa=Ra&&P?Ja()||("CSS1Compat"==Ra.compatMode?m(Pa,10):5):void 0;var Ta=!P||P&&9<=Sa;!Q&&!P||P&&P&&9<=Sa||Q&&T("1.9.1");P&&T("9");var Ua=function(a,b){var c;c=a.className;c=J(c)&&c.match(/\S+/g)||[];for(var d=za(arguments,1),f=c[B]+d[B],e=c,h=0;h<d[B];h++)0<=wa(e,d[h])||e[r](d[h]);a.className=c.join(" ");return c[B]==f};var U=function(a,b){return J(b)?a.getElementById(b):b},Va=function(a,b,c,d){a=d||a;b=b&&"*"!=b?b.toUpperCase():"";if(a.querySelectorAll&&a.querySelector&&(b||c))return a.querySelectorAll(b+(c?"."+c:""));if(c&&a.getElementsByClassName){a=a.getElementsByClassName(c);if(b){d={};for(var f=0,e=0,h;h=a[e];e++)b==h.nodeName&&(d[f++]=h);d.length=f;return d}return a}a=a.getElementsByTagName(b||"*");if(c){d={};for(e=f=0;h=a[e];e++)b=h.className,"function"==typeof b[D]&&0<=wa(b[D](/\s+/),c)&&(d[f++]=h);d.length=
+f;return d}return a},Xa=function(a,b){Aa(b,function(b,d){"style"==d?a[E].cssText=b:"class"==d?a.className=b:"for"==d?a.htmlFor=b:d in Wa?a.setAttribute(Wa[d],b):0==d.lastIndexOf("aria-",0)||0==d.lastIndexOf("data-",0)?a.setAttribute(d,b):a[d]=b})},Wa={cellpadding:"cellPadding",cellspacing:"cellSpacing",colspan:"colSpan",frameborder:"frameBorder",height:"height",maxlength:"maxLength",role:"role",rowspan:"rowSpan",type:"type",usemap:"useMap",valign:"vAlign",width:"width"},Za=function(a,b,c){var d=arguments,
+f=d[0],e=d[1];if(!Ta&&e&&(e[da]||e[A])){f=["<",f];e[da]&&f[r](' name="',sa(e[da]),'"');if(e[A]){f[r](' type="',sa(e[A]),'"');var h={};Ca(h,e);delete h[A];e=h}f[r](">");f=f.join("")}f=g.createElement(f);e&&(J(e)?f.className=e:"array"==I(e)?Ua[ha](null,[f].concat(e)):Xa(f,e));2<d[B]&&Ya(g,f,d,2);return f},Ya=function(a,b,c,d){function f(c){c&&b.appendChild(J(c)?a.createTextNode(c):c)}for(;d<c[B];d++){var e=c[d];if(!ia(e)||ja(e)&&0<e.nodeType)f(e);else{var h;t:{if(e&&"number"==typeof e[B]){if(ja(e)){h=
+"function"==typeof e.item||"string"==typeof e.item;break t}if("function"==I(e)){h="function"==typeof e.item;break t}}h=!1}xa(h?ya(e):e,f)}}};var $a=function(a){var b=a[A];if(void 0===b)return null;switch(b.toLowerCase()){case "checkbox":case "radio":return a[fa]?a[v]:null;case "select-one":return b=a.selectedIndex,0<=b?a.options[b][v]:null;case "select-multiple":for(var b=[],c,d=0;c=a.options[d];d++)c.selected&&b[r](c[v]);return b[B]?b:null;default:return void 0!==a[v]?a[v]:null}};var ab=function(a){ab[" "](a);return a};ab[" "]=function(){};var bb=!P||P&&9<=Sa,cb=P&&!T("9");!R||T("528");Q&&T("1.9b")||P&&T("8")||Ia&&T("9.5")||R&&T("528");Q&&!T("8")||P&&T("9");var V=function(a,b){this.type=a;this.target=b;aa(this,this[ga])};V[C].m=!1;V[C].defaultPrevented=!1;V[C].preventDefault=function(){this.defaultPrevented=!0};var W=function(a,b){a&&this.t(a,b)};la(W,V);G=W[C];G.target=null;G.relatedTarget=null;G.offsetX=0;G.offsetY=0;G.clientX=0;G.clientY=0;G.screenX=0;G.screenY=0;G.button=0;ba(G,0);G.charCode=0;G.ctrlKey=!1;G.altKey=!1;G.shiftKey=!1;G.metaKey=!1;G.o=null;
+G.t=function(a,b){var c=this.type=a[A];V[F](this,c);this.target=a[ga]||a.srcElement;aa(this,b);var d=a.relatedTarget;if(d){if(Q){var f;t:{try{ab(d.nodeName);f=!0;break t}catch(e){}f=!1}f||(d=null)}}else"mouseover"==c?d=a.fromElement:"mouseout"==c&&(d=a.toElement);this.relatedTarget=d;this.offsetX=R||void 0!==a.offsetX?a.offsetX:a.layerX;this.offsetY=R||void 0!==a.offsetY?a.offsetY:a.layerY;this.clientX=void 0!==a.clientX?a.clientX:a.pageX;this.clientY=void 0!==a.clientY?a.clientY:a.pageY;this.screenX=
+a.screenX||0;this.screenY=a.screenY||0;this.button=a.button;ba(this,a[x]||0);this.charCode=a.charCode||("keypress"==c?a[x]:0);this.ctrlKey=a.ctrlKey;this.altKey=a.altKey;this.shiftKey=a.shiftKey;this.metaKey=a.metaKey;this.state=a.state;this.o=a;a.defaultPrevented&&this[ca]();delete this.m};G.preventDefault=function(){W.s[ca][F](this);var a=this.o;if(a[ca])a[ca]();else if(a.returnValue=!1,cb)try{(a.ctrlKey||112<=a[x]&&123>=a[x])&&ba(a,-1)}catch(b){}};var db="closure_listenable_"+(1E6*Math.random()|0),eb=function(a){try{return!(!a||!a[db])}catch(b){return!1}},fb=0;var gb=function(a,b,c,d,f,e){this.c=a;this.g=b;this.src=c;this.type=d;this.capture=!!f;this.j=e;this.key=++fb;this.e=this.k=!1};gb[C].n=function(){this.e=!0;this.j=this.src=this.g=this.c=null};var hb=function(a){this.src=a;this.a={};this.l=0};hb[C].add=function(a,b,c,d,f){var e=this.a[a];e||(e=this.a[a]=[],this.l++);var h;t:{for(h=0;h<e[B];++h){var n=e[h];if(!n.e&&n.c==b&&n.capture==!!d&&n.j==f)break t}h=-1}-1<h?(a=e[h],c||(a.k=!1)):(a=new gb(b,null,this.src,a,!!d,f),a.k=c,e[r](a));return a};hb[C].p=function(a){var b=a[A];if(!(b in this.a))return!1;var c=this.a[b],d=wa(c,a),f;if(f=0<=d)L(null!=c[B]),M.splice[F](c,d,1);f&&(a.n(),0==this.a[b][B]&&(delete this.a[b],this.l--));return f};var ib="closure_lm_"+(1E6*Math.random()|0),X={},lb=0,mb=function(a,b,c,d,f){if("array"==I(b)){for(var e=0;e<b[B];e++)mb(a,b[e],c,d,f);return null}c=nb(c);if(eb(a))a=a.v(b,c,d,f);else{if(!b)throw l("Invalid event type");var e=!!d,h=ob(a);h||(a[ib]=h=new hb(a));c=h.add(b,c,!1,d,f);c.g||(d=pb(),c.g=d,d.src=a,d.c=c,a.addEventListener?a.addEventListener(b,d,e):a.attachEvent(b in X?X[b]:X[b]="on"+b,d),lb++);a=c}return a},pb=function(){var a=qb,b=bb?function(c){return a[F](b.src,b.c,c)}:function(c){c=a[F](b.src,
+b.c,c);if(!c)return c};return b},sb=function(a,b,c,d){var f=1;if(a=ob(a))if(b=a.a[b])for(b=ya(b),a=0;a<b[B];a++){var e=b[a];e&&e.capture==c&&!e.e&&(f&=!1!==rb(e,d))}return Boolean(f)},rb=function(a,b){var c=a.c,d=a.j||a.src;if(a.k&&"number"!=typeof a&&a&&!a.e){var f=a.src;if(eb(f))f.u(a);else{var e=a[A],h=a.g;f.removeEventListener?f.removeEventListener(e,h,a.capture):f.detachEvent&&f.detachEvent(e in X?X[e]:X[e]="on"+e,h);lb--;(e=ob(f))?(e.p(a),0==e.l&&(e.src=null,f[ib]=null)):a.n()}}return c[F](d,
+b)},qb=function(a,b){if(a.e)return!0;if(!bb){var c;if(!(c=b))t:{c=["window","event"];for(var d=H,f;f=c[s]();)if(null!=d[f])d=d[f];else{c=null;break t}c=d}f=c;c=new W(f,this);d=!0;if(!(0>f[x]||void 0!=f.returnValue)){t:{var e=!1;if(0==f[x])try{ba(f,-1);break t}catch(h){e=!0}if(e||void 0==f.returnValue)f.returnValue=!0}f=[];for(e=c.currentTarget;e;e=e.parentNode)f[r](e);for(var e=a[A],n=f[B]-1;!c.m&&0<=n;n--)aa(c,f[n]),d&=sb(f[n],e,!0,c);for(n=0;!c.m&&n<f[B];n++)aa(c,f[n]),d&=sb(f[n],e,!1,c)}return d}return rb(a,
+new W(b,this))},ob=function(a){a=a[ib];return a instanceof hb?a:null},tb="__closure_events_fn_"+(1E9*Math.random()>>>0),nb=function(a){L(a,"Listener can not be null.");if("function"==I(a))return a;L(a.handleEvent,"An object listener must have handleEvent method.");return a[tb]||(a[tb]=function(b){return a.handleEvent(b)})};var ub=function(a,b,c){var d;t:if(d=ta(c),void 0===a[E][d]&&(c=(R?"Webkit":Q?"Moz":P?"ms":Ia?"O":null)+ua(c),void 0!==a[E][c])){d=c;break t}d&&(a[E][d]=b)};var vb=function(a,b){var c=[];1<arguments[B]&&(c=k[C][t][F](arguments)[t](1));var d=Va(g,"th","tct-selectall",a);if(0!=d[B]){var d=d[0],f=0,e=Va(g,"tbody",null,a);e[B]&&(f=e[0].rows[B]);this.d=Za("input",{type:"checkbox"});d.appendChild(this.d);f?mb(this.d,"click",this.r,!1,this):q(this.d,!0);this.f=[];this.h=[];this.i=[];d=Va(g,"input",null,a);for(f=0;e=d[f];f++)"checkbox"==e[A]&&e!=this.d?(this.f[r](e),mb(e,"click",this.q,!1,this)):"action"==e[da]&&(0<=c[w](e[v])?this.i[r](e):this.h[r](e),q(e,!0))}};
+G=vb[C];G.f=null;G.b=0;G.d=null;G.h=null;G.i=null;G.r=function(a){for(var b=a[ga][fa],c=a=0,d;d=this.f[c];c++)d.checked=b,a+=1;this.b=b?this.f[B]:0;for(c=0;b=this.h[c];c++)q(b,!this.b);for(c=0;b=this.i[c];c++)q(b,1!=a?!0:!1)};G.q=function(a){this.b+=a[ga][fa]?1:-1;this.d.checked=this.b==this.f[B];a=0;for(var b;b=this.h[a];a++)q(b,!this.b);for(a=0;b=this.i[a];a++)q(b,1!=this.b?!0:!1)};var wb=function(){var a=U(g,"kinds");a&&new vb(a);(a=U(g,"pending_backups"))&&new vb(a);(a=U(g,"backups"))&&new vb(a,"Restore");var b=U(g,"ae-datastore-admin-filesystem");b&&mb(b,"change",function(){var a="gs"==$a(b);U(g,"gs_bucket_tr")[E].display=a?"":"none"});if(a=U(g,"confirm_delete_form")){var c=U(g,"confirm_readonly_delete");c&&(a.onsubmit=function(){var a=U(g,"confirm_message");J("color")?ub(a,"red","color"):Aa("color",ka(ub,a));return c[fa]})}},Y=["ae","Datastore","Admin","init"],Z=H;
+Y[0]in Z||!Z.execScript||Z.execScript("var "+Y[0]);for(var $;Y[B]&&($=Y[s]());)Y[B]||void 0===wb?Z=Z[$]?Z[$]:Z[$]={}:Z[$]=wb;
diff --git a/google/appengine/ext/datastore_admin/utils.py b/google/appengine/ext/datastore_admin/utils.py
index bb269bf..b20203c 100644
--- a/google/appengine/ext/datastore_admin/utils.py
+++ b/google/appengine/ext/datastore_admin/utils.py
@@ -688,9 +688,9 @@
 class ReserveKey(mr_operation.Operation):
   """Mapper operation to reserve key ids."""
 
-  def __init__(self, key, app_id):
+  def __init__(self, key):
     self.key = key
-    self.app_id = app_id
+    self.app_id = key.app()
     self.pool_id = 'reserve_key_%s_pool' % self.app_id
 
   def __call__(self, ctx):
diff --git a/google/appengine/ext/endpoints/__init__.py b/google/appengine/ext/endpoints/__init__.py
index 3a77713..09529c2 100644
--- a/google/appengine/ext/endpoints/__init__.py
+++ b/google/appengine/ext/endpoints/__init__.py
@@ -18,20 +18,55 @@
 
 
 
-"""Apiserving Module."""
+"""Deprecated endpoints module from google.appengine.ext."""
 
 
 
 
 
 
-from api_config import api
-from api_config import API_EXPLORER_CLIENT_ID
-from api_config import EMAIL_SCOPE
-from api_config import method
-from api_exceptions import *
-from apiserving import *
-import message_parser
-from users_id_token import get_current_user
-from users_id_token import InvalidGetUserCall
-from users_id_token import SKIP_CLIENT_ID_CHECK
+import itertools
+import logging
+import os
+import re
+import sys
+
+logging.warning('Importing endpoints from google.appengine.ext is deprecated '
+                'and will be removed.  Add the endpoints library to '
+                'app.yaml, then endpoints can be imported simply with '
+                '"import endpoints".')
+
+
+if 'APPENGINE_RUNTIME' not in os.environ:
+
+
+  if not hasattr(sys, 'version_info'):
+    raise RuntimeError('Endpoints library isn\'t available in older Python '
+                       'runtime environments. Use the python27 runtime.')
+  version_tuple = tuple(sys.version_info[:2])
+  if version_tuple < (2, 7):
+    raise RuntimeError('Endpoints library isn\'t available in python %d.%d. '
+                       'Use version 2.7 or greater.' % version_tuple)
+elif os.environ['APPENGINE_RUNTIME'] == 'python':
+  raise RuntimeError('Endpoints library isn\'t available in python 2.5 '
+                     'runtime. Use the python27 runtime instead.')
+
+
+
+
+
+for path in sys.path:
+  lib_path, final_dir = os.path.split(path)
+  if re.match('webapp2-.+', final_dir):
+    endpoints_path = os.path.join(lib_path, 'endpoints-1.0')
+    if endpoints_path not in sys.path:
+      sys.path.append(endpoints_path)
+    break
+
+
+
+from endpoints import *
+
+
+
+__version__ = '1.0'
diff --git a/google/appengine/ext/mapreduce/context.py b/google/appengine/ext/mapreduce/context.py
index 9135cdf..7513516 100644
--- a/google/appengine/ext/mapreduce/context.py
+++ b/google/appengine/ext/mapreduce/context.py
@@ -38,40 +38,35 @@
 """
 
 
-__all__ = [
-           "get",
+__all__ = ["get",
+           "Pool",
            "Context",
-           "Counters",
-           "EntityList",
-           "ItemList",
-           "MutationPool",
            "COUNTER_MAPPER_CALLS",
            "COUNTER_MAPPER_WALLTIME_MS",
            "DATASTORE_DEADLINE",
            "MAX_ENTITY_COUNT",
-           "MAX_POOL_SIZE",
-           ]
+          ]
 
+import heapq
+import logging
 import threading
 
-from google.appengine.api import datastore
-from google.appengine.ext import db
-
 try:
   from google.appengine.ext import ndb
 except ImportError:
   ndb = None
-# It is acceptable to set key_range.ndb to the ndb module,
-# imported through some other way (e.g. from the app dir).
+from google.appengine.api import datastore
+from google.appengine.ext import db
+from google.appengine.runtime import apiproxy_errors
 
 
 
 
 
-MAX_POOL_SIZE = 900 * 1000
 
 
-MAX_ENTITY_COUNT = 500
+
+MAX_ENTITY_COUNT = 20
 
 
 DATASTORE_DEADLINE = 15
@@ -84,6 +79,10 @@
 COUNTER_MAPPER_WALLTIME_MS = "mapper-walltime-ms"
 
 
+
+
+
+
 def _normalize_entity(value):
   """Return an entity from an entity or model instance."""
   if ndb is not None and isinstance(value, ndb.Model):
@@ -92,6 +91,7 @@
     return value._populate_internal_entity()
   return value
 
+
 def _normalize_key(value):
   """Return a key from an entity, model instance, key, or key string."""
   if ndb is not None and isinstance(value, (ndb.Model, ndb.Key)):
@@ -103,8 +103,12 @@
   else:
     return value
 
-class ItemList(object):
-  """Holds list of arbitrary items, and their total size.
+
+class _ItemList(object):
+  """A buffer that holds arbitrary items and auto flushes them when full.
+
+  Callers of this class provides the logic on how to flush.
+  This class takes care of the common logic of when to flush and when to retry.
 
   Properties:
     items: list of objects.
@@ -112,69 +116,150 @@
     size: aggregate item size in bytes.
   """
 
-  def __init__(self):
-    """Constructor."""
-    self.items = []
-    self.length = 0
-    self.size = 0
+  DEFAULT_RETRIES = 3
+  _LARGEST_ITEMS_TO_LOG = 5
 
-  def append(self, item, item_size):
+  def __init__(self,
+               max_entity_count,
+               flush_function,
+               timeout_retries=DEFAULT_RETRIES,
+               repr_function=None):
+    """Constructor.
+
+    Args:
+      max_entity_count: maximum number of entities before flushing it to db.
+      flush_function: a function that can flush the items. The function is
+        called with a list of items as the first argument, a dict of options
+        as second argument. Currently options can contain {"deadline": int}.
+        see self.flush on how the function is called.
+      timeout_retries: how many times to retry upon timeouts.
+      repr_function: a function that turns an item into meaningful
+        representation. For debugging large items.
+    """
+    self.items = []
+    self.__max_entity_count = max_entity_count
+    self.__flush_function = flush_function
+    self.__repr_function = repr_function
+    self.__timeout_retries = timeout_retries
+
+  def __str__(self):
+    return "ItemList of with %s items" % len(self.items)
+
+  def append(self, item):
     """Add new item to the list.
 
+    If needed, append will first flush existing items and clear existing items.
+
     Args:
       item: an item to add to the list.
-      item_size: item size in bytes as int.
     """
+    if self.should_flush():
+      self.flush()
     self.items.append(item)
-    self.length += 1
-    self.size += item_size
+
+  def flush(self):
+    """Force a flush."""
+    if not self.items:
+      return
+
+    retry = 0
+    options = {"deadline": DATASTORE_DEADLINE}
+    while retry <= self.__timeout_retries:
+      try:
+        self.__flush_function(self.items, options)
+        self.clear()
+        break
+      except db.Timeout, e:
+        logging.warning(e)
+        logging.warning("Flushing '%s' timed out. Will retry for the %s time.",
+                        self, retry)
+        retry += 1
+        options["deadline"] *= 2
+      except apiproxy_errors.RequestTooLargeError:
+        self._log_largest_items()
+        raise
+    else:
+      raise
+
+  def _log_largest_items(self):
+    if not self.__repr_function:
+      logging.error("Got RequestTooLargeError but can't interpret items in "
+                    "_ItemList %s.", self)
+      return
+
+    sizes = [len(self.__repr_function(i)) for i in self.items]
+    largest = heapq.nlargest(self._LARGEST_ITEMS_TO_LOG,
+                             zip(sizes, self.items),
+                             lambda t: t[0])
+
+    self._largest = [(s, self.__repr_function(i)) for s, i in largest]
+    logging.error("Got RequestTooLargeError. Largest items: %r", self._largest)
 
   def clear(self):
     """Clear item list."""
     self.items = []
-    self.length = 0
-    self.size = 0
 
-  @property
-  def entities(self):
-    """Return items. For backwards compatability."""
-    return self.items
+  def should_flush(self):
+    """Whether to flush before append the next entity.
+
+    Returns:
+      True to flush. False other.
+    """
+    return len(self.items) >= self.__max_entity_count
 
 
+class Pool(object):
+  """Mutation pool accumulates changes to perform them in patch.
 
-EntityList = ItemList
+  Any Pool subclass should not be public. Instead, Pool should define an
+  operation.Operation class and let user uses that. For example, in a map
+  function, user can do:
+
+  def map(foo):
+    yield OperationOnMyPool(any_argument)
+
+  Since Operation is a callable object, Mapreduce library will invoke
+  any Operation object that is yielded with context.Context instance.
+  The operation object can then access MyPool from Context.get_pool.
+  """
+
+  def flush(self):
+    """Flush all changes."""
+    raise NotImplementedError()
 
 
-
-class MutationPool(object):
+class _MutationPool(Pool):
   """Mutation pool accumulates datastore changes to perform them in batch.
 
   Properties:
-    puts: ItemList of entities to put to datastore.
-    deletes: ItemList of keys to delete from datastore.
-    max_pool_size: maximum single list pool size. List changes will be flushed
-      when this size is reached.
+    puts: _ItemList of entities to put to datastore.
+    deletes: _ItemList of keys to delete from datastore.
+    ndb_puts: _ItemList of ndb entities to put to datastore.
+    ndb_deletes: _ItemList of ndb keys to delete from datastore.
   """
 
   def __init__(self,
-               max_pool_size=MAX_POOL_SIZE,
                max_entity_count=MAX_ENTITY_COUNT,
                mapreduce_spec=None):
     """Constructor.
 
     Args:
-      max_pool_size: maximum pools size in bytes before flushing it to db.
       max_entity_count: maximum number of entities before flushing it to db.
       mapreduce_spec: An optional instance of MapperSpec.
     """
-    self.max_pool_size = max_pool_size
     self.max_entity_count = max_entity_count
     params = mapreduce_spec.params if mapreduce_spec is not None else {}
     self.force_writes = bool(params.get("force_ops_writes", False))
-    self.puts = ItemList()
-    self.deletes = ItemList()
-    self.ndb_puts = ItemList()
-    self.ndb_deletes = ItemList()
+    self.puts = _ItemList(max_entity_count,
+                          self._flush_puts,
+                          self._db_repr)
+    self.deletes = _ItemList(max_entity_count,
+                             self._flush_deletes)
+    self.ndb_puts = _ItemList(max_entity_count,
+                              self._flush_ndb_puts,
+                              self._ndb_repr)
+    self.ndb_deletes = _ItemList(max_entity_count,
+                                 self._flush_ndb_deletes)
 
   def put(self, entity):
     """Registers entity to put to datastore.
@@ -185,20 +270,12 @@
     actual_entity = _normalize_entity(entity)
     if actual_entity is None:
       return self.ndb_put(entity)
-    entity_size = len(actual_entity._ToPb().Encode())
-    if (self.puts.length >= self.max_entity_count or
-        (self.puts.size + entity_size) > self.max_pool_size):
-      self.__flush_puts()
-    self.puts.append(actual_entity, entity_size)
+    self.puts.append(actual_entity)
 
   def ndb_put(self, entity):
     """Like put(), but for NDB entities."""
     assert ndb is not None and isinstance(entity, ndb.Model)
-    entity_size = len(entity._to_pb().Encode())
-    if (self.ndb_puts.length >= self.max_entity_count or
-        (self.ndb_puts.size + entity_size) > self.max_pool_size):
-      self.__flush_ndb_puts()
-    self.ndb_puts.append(entity, entity_size)
+    self.ndb_puts.append(entity)
 
   def delete(self, entity):
     """Registers entity to delete from datastore.
@@ -206,73 +283,81 @@
     Args:
       entity: an entity, model instance, or key to delete.
     """
-
     key = _normalize_key(entity)
     if key is None:
       return self.ndb_delete(entity)
-    key_size = len(key._ToPb().Encode())
-    if (self.deletes.length >= self.max_entity_count or
-        (self.deletes.size + key_size) > self.max_pool_size):
-      self.__flush_deletes()
-    self.deletes.append(key, key_size)
+    self.deletes.append(key)
 
   def ndb_delete(self, entity_or_key):
     """Like delete(), but for NDB entities/keys."""
-    if isinstance(entity_or_key, ndb.Model):
+    if ndb is not None and isinstance(entity_or_key, ndb.Model):
       key = entity_or_key.key
     else:
       key = entity_or_key
-    key_size = len(key.reference().Encode())
-    if (self.ndb_deletes.length >= self.max_entity_count or
-        (self.ndb_deletes.size + key_size) > self.max_pool_size):
-      self.__flush_ndb_deletes()
-    self.ndb_deletes.append(key, key_size)
-
+    self.ndb_deletes.append(key)
 
   def flush(self):
     """Flush(apply) all changed to datastore."""
-    self.__flush_puts()
-    self.__flush_deletes()
-    self.__flush_ndb_puts()
-    self.__flush_ndb_deletes()
+    self.puts.flush()
+    self.deletes.flush()
+    self.ndb_puts.flush()
+    self.ndb_deletes.flush()
 
-  def __flush_puts(self):
+  @classmethod
+  def _db_repr(cls, entity):
+    """Converts entity to a readable repr.
+
+    Args:
+      entity: datastore.Entity or datastore_types.Key.
+
+    Returns:
+      Proto in str.
+    """
+    return str(entity._ToPb())
+
+  @classmethod
+  def _ndb_repr(cls, entity):
+    """Converts entity to a readable repr.
+
+    Args:
+      entity: ndb.Model
+
+    Returns:
+      Proto in str.
+    """
+    return str(entity._to_pb())
+
+  def _flush_puts(self, items, options):
     """Flush all puts to datastore."""
-    if self.puts.length:
-      datastore.Put(self.puts.items, config=self.__create_config())
-    self.puts.clear()
+    datastore.Put(items, config=self._create_config(options))
 
-  def __flush_deletes(self):
+  def _flush_deletes(self, items, options):
     """Flush all deletes to datastore."""
-    if self.deletes.length:
-      datastore.Delete(self.deletes.items, config=self.__create_config())
-    self.deletes.clear()
+    datastore.Delete(items, config=self._create_config(options))
 
-  def __flush_ndb_puts(self):
+  def _flush_ndb_puts(self, items, options):
     """Flush all NDB puts to datastore."""
-    if self.ndb_puts.length:
-      ndb.put_multi(self.ndb_puts.items, config=self.__create_config())
-    self.ndb_puts.clear()
+    assert ndb is not None
+    ndb.put_multi(items, config=self._create_config(options))
 
-  def __flush_ndb_deletes(self):
+  def _flush_ndb_deletes(self, items, options):
     """Flush all deletes to datastore."""
-    if self.ndb_deletes.length:
-      ndb.delete_multi(self.ndb_deletes.items, config=self.__create_config())
-    self.ndb_deletes.clear()
+    assert ndb is not None
+    ndb.delete_multi(items, config=self._create_config(options))
 
-  def __create_config(self):
+  def _create_config(self, options):
     """Creates datastore Config.
 
     Returns:
       A datastore_rpc.Configuration instance.
     """
-    return datastore.CreateConfig(deadline=DATASTORE_DEADLINE,
+    return datastore.CreateConfig(deadline=options["deadline"],
                                   force_writes=self.force_writes)
 
 
 
 
-class Counters(object):
+class _Counters(Pool):
   """Regulates access to counters."""
 
   def __init__(self, shard_state):
@@ -303,8 +388,6 @@
   Properties:
     mapreduce_spec: current mapreduce specification as model.MapreduceSpec.
     shard_state: current shard state as model.ShardState.
-    mutation_pool: current mutation pool as MutationPool.
-    counters: counters object as Counters.
   """
 
 
@@ -316,6 +399,7 @@
     Args:
       mapreduce_spec: mapreduce specification as model.MapreduceSpec.
       shard_state: shard state as model.ShardState.
+      task_retry_count: how many times this task has been retried.
     """
     self.mapreduce_spec = mapreduce_spec
     self.shard_state = shard_state
@@ -332,14 +416,16 @@
 
       self.shard_id = None
 
-    self.mutation_pool = MutationPool(
-        max_pool_size=(MAX_POOL_SIZE/(2**self.task_retry_count)),
-        max_entity_count=(MAX_ENTITY_COUNT/(2**self.task_retry_count)),
-        mapreduce_spec=mapreduce_spec)
-    self.counters = Counters(shard_state)
+
+
+    self._mutation_pool = _MutationPool(mapreduce_spec=mapreduce_spec)
+    self._counters = _Counters(shard_state)
+
+
+    self.counters = self._counters
 
     self._pools = {}
-    self.register_pool("mutation_pool", self.mutation_pool)
+    self.register_pool("mutation_pool", self._mutation_pool)
     self.register_pool("counters", self.counters)
 
   def flush(self):
@@ -347,18 +433,12 @@
     for pool in self._pools.values():
       pool.flush()
 
-
-
-
-
-
-
   def register_pool(self, key, pool):
     """Register an arbitrary pool to be flushed together with this context.
 
     Args:
       key: pool key as string.
-      pool: a pool instance. Pool should implement flush(self) method.
+      pool: a pool instance.
     """
     self._pools[key] = pool
 
diff --git a/google/appengine/ext/mapreduce/errors.py b/google/appengine/ext/mapreduce/errors.py
index ede74f9..8ddea2d 100644
--- a/google/appengine/ext/mapreduce/errors.py
+++ b/google/appengine/ext/mapreduce/errors.py
@@ -45,6 +45,7 @@
     "RetrySliceError",
     "SHARD_RETRY_ERRORS",
     "ShuffleServiceError",
+    "InvalidRecordError",
     ]
 
 from google.appengine.api import files
@@ -110,3 +111,7 @@
   The job will be failed if the slice can't progress before maximum
   number of retries.
   """
+
+
+class InvalidRecordError(Error):
+  """Raised when invalid record encountered."""
diff --git a/google/appengine/ext/mapreduce/handlers.py b/google/appengine/ext/mapreduce/handlers.py
index 27e3688..94b811e 100644
--- a/google/appengine/ext/mapreduce/handlers.py
+++ b/google/appengine/ext/mapreduce/handlers.py
@@ -1014,10 +1014,8 @@
       base_path: handler_base path.
     """
     config = util.create_datastore_write_config(mapreduce_spec)
-
-    queue_name = mapreduce_spec.params.get(
-        model.MapreduceSpec.PARAM_DONE_CALLBACK_QUEUE,
-        "default")
+    queue_name = util.get_queue_name(mapreduce_spec.params.get(
+        model.MapreduceSpec.PARAM_DONE_CALLBACK_QUEUE))
     done_callback = mapreduce_spec.params.get(
         model.MapreduceSpec.PARAM_DONE_CALLBACK)
     done_task = None
diff --git a/google/appengine/ext/mapreduce/input_readers.py b/google/appengine/ext/mapreduce/input_readers.py
index 014d5ca..ef577cb 100644
--- a/google/appengine/ext/mapreduce/input_readers.py
+++ b/google/appengine/ext/mapreduce/input_readers.py
@@ -28,8 +28,6 @@
 
 
 
-
-
 """Defines input readers for MapReduce."""
 
 
@@ -74,10 +72,7 @@
 from google.appengine.api import datastore
 from google.appengine.api import files
 from google.appengine.api import logservice
-from google.appengine.api.files import records
 from google.appengine.api.logservice import log_service_pb
-from google.appengine.datastore import datastore_query
-from google.appengine.datastore import datastore_rpc
 from google.appengine.ext import blobstore
 from google.appengine.ext import db
 from google.appengine.ext import key_range
@@ -92,6 +87,7 @@
 from google.appengine.ext.mapreduce import namespace_range
 from google.appengine.ext.mapreduce import operation
 from google.appengine.ext.mapreduce import property_range
+from google.appengine.ext.mapreduce import records
 from google.appengine.ext.mapreduce import util
 
 
diff --git a/google/appengine/ext/mapreduce/operation/counters.py b/google/appengine/ext/mapreduce/operation/counters.py
index 33342eb..1dfba69 100644
--- a/google/appengine/ext/mapreduce/operation/counters.py
+++ b/google/appengine/ext/mapreduce/operation/counters.py
@@ -39,6 +39,8 @@
 from google.appengine.ext.mapreduce.operation import base
 
 
+
+
 class Increment(base.Operation):
   """Increment counter operation."""
 
@@ -58,4 +60,4 @@
     Args:
       context: mapreduce context as context.Context.
     """
-    context.counters.increment(self.counter_name, self.delta)
+    context._counters.increment(self.counter_name, self.delta)
diff --git a/google/appengine/ext/mapreduce/operation/db.py b/google/appengine/ext/mapreduce/operation/db.py
index 65759ad..31899b0 100644
--- a/google/appengine/ext/mapreduce/operation/db.py
+++ b/google/appengine/ext/mapreduce/operation/db.py
@@ -44,7 +44,7 @@
 class Put(base.Operation):
   """Put entity into datastore via mutation_pool.
 
-  See mapreduce.context.MutationPool.
+  See mapreduce.context._MutationPool.
   """
 
   def __init__(self, entity):
@@ -61,13 +61,13 @@
     Args:
       context: mapreduce context as context.Context.
     """
-    context.mutation_pool.put(self.entity)
+    context._mutation_pool.put(self.entity)
 
 
 class Delete(base.Operation):
   """Delete entity from datastore via mutation_pool.
 
-  See mapreduce.context.MutationPool.
+  See mapreduce.context._MutationPool.
   """
 
   def __init__(self, entity):
@@ -84,4 +84,4 @@
     Args:
       context: mapreduce context as context.Context.
     """
-    context.mutation_pool.delete(self.entity)
+    context._mutation_pool.delete(self.entity)
diff --git a/google/appengine/ext/mapreduce/output_writers.py b/google/appengine/ext/mapreduce/output_writers.py
index 707052b..afa0e24 100644
--- a/google/appengine/ext/mapreduce/output_writers.py
+++ b/google/appengine/ext/mapreduce/output_writers.py
@@ -52,6 +52,7 @@
 
 
 
+import cStringIO
 import gc
 import logging
 import pickle
@@ -60,10 +61,11 @@
 
 from google.appengine.api import files
 from google.appengine.api.files import file_service_pb
-from google.appengine.api.files import records
+from google.appengine.ext.mapreduce import context
 from google.appengine.ext.mapreduce import errors
 from google.appengine.ext.mapreduce import model
 from google.appengine.ext.mapreduce import operation
+from google.appengine.ext.mapreduce import records
 
 
 
@@ -286,7 +288,7 @@
   return params
 
 
-class _FilePool(object):
+class _FilePool(context.Pool):
   """Pool of file append operations."""
 
   def __init__(self, flush_size_chars=_FILES_API_FLUSH_SIZE, ctx=None):
@@ -347,53 +349,7 @@
     self._size = 0
 
 
-class _StringWriter(object):
-  """Simple writer for records api that writes to a string buffer."""
-
-  def __init__(self):
-    self._buffer = ""
-
-  def to_string(self):
-    """Convert writer buffer to string."""
-    return self._buffer
-
-  def write(self, data):
-    """Write data.
-
-    Args:
-      data: data to append to the buffer as string.
-    """
-    self._buffer += data
-
-
-class _PassthroughWriter(object):
-  """Simple output writer that exposes a file-like write().
-
-  Handles the mismatch of an output writer's write(), which requires a context,
-  and a file-like write() which does not. The context is provided at init time
-  and used with each write call.
-  """
-
-  def __init__(self, writer, ctx):
-    """Initialize passthrough writer.
-
-    Args:
-      writer: the underlying mapreduce output writer.
-      ctx: the mapreduce context to pass the writer on each write.
-    """
-    self._writer = writer
-    self._ctx = ctx
-
-  def write(self, data):
-    """Write data.
-
-    Args:
-      data: data to write
-    """
-    self._writer.write(data, self._ctx)
-
-
-class RecordsPool(object):
+class RecordsPool(context.Pool):
   """Pool of append operations for records files."""
 
 
@@ -439,12 +395,14 @@
   def flush(self):
     """Flush pool contents."""
 
-    buf = _StringWriter()
+    buf = cStringIO.StringIO()
     with records.RecordsWriter(buf) as w:
       for record in self._buffer:
         w.write(record)
+      w._pad_block()
+    str_buf = buf.getvalue()
+    buf.close()
 
-    str_buf = buf.to_string()
     if not self._exclusive and len(str_buf) > _FILES_API_MAX_SIZE:
 
       raise errors.Error(
@@ -1063,16 +1021,10 @@
   def write(self, data, ctx):
     """Write data to the GoogleCloudStorage file.
 
-    The actual writing to the stream is handled by a private function
-    allowing this method to be overriden with other logic (such as records).
-
     Args:
       data: string containing the data to be written.
       ctx: a model.Context for this shard.
     """
-    self._write(data, ctx)
-
-  def _write(self, data, ctx):
     start_time = time.time()
     self._streaming_buffer.write(data)
     if ctx:
@@ -1086,6 +1038,33 @@
     shard_state.writer_state = {"filename": self._filename}
 
 
+class _PassthroughWriter(object):
+  """Interface adapter from file-like write() to output writer write().
+
+  Handles the mismatch of an output writer's write(), which requires a context,
+  and a file-like write() which does not. The context is provided at init time
+  and used with each write call.
+  """
+
+  def __init__(self, output_writer, ctx):
+    """Initialize passthrough writer.
+
+    Args:
+      output_writer: the underlying mapreduce output writer.
+      ctx: the mapreduce context to pass to the output writer on each write.
+    """
+    self._output_writer = output_writer
+    self._ctx = ctx
+
+  def write(self, data):
+    """Write data.
+
+    Args:
+      data: data to write
+    """
+    self._output_writer.write(data, self._ctx)
+
+
 class _GoogleCloudStorageRecordOutputWriter(_GoogleCloudStorageOutputWriter):
   """Write data to the Google Cloud Storage file using LevelDB format.
 
@@ -1130,7 +1109,6 @@
     self._reset()
 
   def to_json(self):
-
     if self._buffer:
       self._flush(self._last_ctx)
     return super(_GoogleCloudStorageRecordOutputWriter, self).to_json()
@@ -1163,16 +1141,14 @@
     record_writer = records.RecordsWriter(
         _PassthroughWriter(super(_GoogleCloudStorageRecordOutputWriter, self),
                            ctx))
-
     with record_writer as w:
       for record in self._buffer:
         w.write(record)
+      w._pad_block()
     self._reset()
 
   def _reset(self):
     self._buffer = []
 
-
-
     self._size = 0
     self._last_ctx = None
diff --git a/google/appengine/ext/mapreduce/parameters.py b/google/appengine/ext/mapreduce/parameters.py
index 7e5d6c4..ba10d84 100644
--- a/google/appengine/ext/mapreduce/parameters.py
+++ b/google/appengine/ext/mapreduce/parameters.py
@@ -16,40 +16,89 @@
 #
 """Parameters to control Mapreduce."""
 
+__all__ = ["CONFIG_NAMESPACE",
+           "config"]
 
-__all__ = []
+from google.appengine.api import lib_config
 
-DEFAULT_SHARD_RETRY_LIMIT = 3
-DEFAULT_QUEUE_NAME = "default"
-DEFAULT_SHARD_COUNT = 8
+CONFIG_NAMESPACE = "mapreduce"
+
+
+class _ConfigDefaults(object):
+  """Default configs.
+
+  Do not change parameters starts with _.
+
+  SHARD_RETRY_LIMIT: How many times a shard can retry.
+
+  QUEUE_NAME: Default queue for MR.
+
+  SHARD_COUNT: Default shard count.
+
+  PROCESSING_RATE_PER_SEC: Default rate of processed entities per second.
+
+  BASE_PATH : Base path of mapreduce and pipeline handlers.
+
+  RETRY_SLICE_ERROR_MAX_RETRIES:
+    How many times to cope with a RetrySliceError before totally
+  giving up and aborting the whole job. RetrySliceError is raised only
+  during processing user data. Errors from MR framework are not counted.
+
+  MAX_TASK_RETRIES: How many times to retry a task before dropping it.
+  """
+
+  SHARD_RETRY_LIMIT = 3
+
+  QUEUE_NAME = "default"
+
+  SHARD_COUNT = 8
+
+
+  PROCESSING_RATE_PER_SEC = 1000000
+
+
+  BASE_PATH = "/_ah/mapreduce"
+
+  RETRY_SLICE_ERROR_MAX_RETRIES = 10
+
+
+  MAX_TASK_RETRIES = 30
+
+
+
+
+  _SLICE_DURATION_SEC = 15
+
+
+  _LEASE_GRACE_PERIOD = 1
+
+
+  _REQUEST_EVENTUAL_TIMEOUT = 10 * 60 + 30
+
+
+  _CONTROLLER_PERIOD_SEC = 2
+
+
+config = lib_config.register(CONFIG_NAMESPACE, _ConfigDefaults.__dict__)
 
 
 
 
 
-_RETRY_SLICE_ERROR_MAX_RETRIES = 10
-
-
-_MAX_TASK_RETRIES = 30
+_DEFAULT_PIPELINE_BASE_PATH = config.BASE_PATH + "/pipeline"
 
 
 
 
-_SLICE_DURATION_SEC = 15
 
-
-_LEASE_GRACE_PERIOD = 1
-
-
-_REQUEST_EVENTUAL_TIMEOUT = 10 * 60 + 30
-
-
-_CONTROLLER_PERIOD_SEC = 2
-
-
-
-_DEFAULT_PROCESSING_RATE_PER_SEC = 1000000
-
-
-_DEFAULT_BASE_PATH = "/_ah/mapreduce"
-_DEFAULT_PIPELINE_BASE_PATH = _DEFAULT_BASE_PATH + "/pipeline"
+DEFAULT_SHARD_RETRY_LIMIT = config.SHARD_RETRY_LIMIT
+DEFAULT_QUEUE_NAME = config.QUEUE_NAME
+DEFAULT_SHARD_COUNT = config.SHARD_COUNT
+_DEFAULT_PROCESSING_RATE_PER_SEC = config.PROCESSING_RATE_PER_SEC
+_DEFAULT_BASE_PATH = config.BASE_PATH
+_RETRY_SLICE_ERROR_MAX_RETRIES = config.RETRY_SLICE_ERROR_MAX_RETRIES
+_MAX_TASK_RETRIES = config.MAX_TASK_RETRIES
+_SLICE_DURATION_SEC = config._SLICE_DURATION_SEC
+_LEASE_GRACE_PERIOD = config._LEASE_GRACE_PERIOD
+_REQUEST_EVENTUAL_TIMEOUT = config._REQUEST_EVENTUAL_TIMEOUT
+_CONTROLLER_PERIOD_SEC = config._CONTROLLER_PERIOD_SEC
diff --git a/google/appengine/ext/mapreduce/records.py b/google/appengine/ext/mapreduce/records.py
new file mode 100644
index 0000000..35647da
--- /dev/null
+++ b/google/appengine/ext/mapreduce/records.py
@@ -0,0 +1,351 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Lightweight record format.
+
+This format implements log file format from leveldb:
+http://leveldb.googlecode.com/svn/trunk/doc/log_format.txt
+
+Full specification of format follows in case leveldb decides to change it.
+
+
+The log file contents are a sequence of 32KB blocks.  The only
+exception is that the tail of the file may contain a partial block.
+
+Each block consists of a sequence of records:
+   block := record* trailer?
+   record :=
+      checksum: uint32  // masked crc32c of type and data[]
+      length: uint16
+      type: uint8       // One of FULL, FIRST, MIDDLE, LAST
+      data: uint8[length]
+
+A record never starts within the last six bytes of a block (since it
+won't fit).  Any leftover bytes here form the trailer, which must
+consist entirely of zero bytes and must be skipped by readers.
+
+Aside: if exactly seven bytes are left in the current block, and a new
+non-zero length record is added, the writer must emit a FIRST record
+(which contains zero bytes of user data) to fill up the trailing seven
+bytes of the block and then emit all of the user data in subsequent
+blocks.
+
+More types may be added in the future.  Some Readers may skip record
+types they do not understand, others may report that some data was
+skipped.
+
+FULL == 1
+FIRST == 2
+MIDDLE == 3
+LAST == 4
+
+The FULL record contains the contents of an entire user record.
+
+FIRST, MIDDLE, LAST are types used for user records that have been
+split into multiple fragments (typically because of block boundaries).
+FIRST is the type of the first fragment of a user record, LAST is the
+type of the last fragment of a user record, and MID is the type of all
+interior fragments of a user record.
+
+Example: consider a sequence of user records:
+   A: length 1000
+   B: length 97270
+   C: length 8000
+A will be stored as a FULL record in the first block.
+
+B will be split into three fragments: first fragment occupies the rest
+of the first block, second fragment occupies the entirety of the
+second block, and the third fragment occupies a prefix of the third
+block.  This will leave six bytes free in the third block, which will
+be left empty as the trailer.
+
+C will be stored as a FULL record in the fourth block.
+
+"""
+
+__all__ = ['RecordsWriter',
+           'RecordsReader']
+
+import logging
+import struct
+
+import google
+
+
+from google.appengine.api.files import crc32c
+from google.appengine.ext.mapreduce import errors
+
+
+
+
+
+_BLOCK_SIZE = 32 * 1024
+
+
+_HEADER_FORMAT = '<IHB'
+
+
+_HEADER_LENGTH = struct.calcsize(_HEADER_FORMAT)
+
+
+_RECORD_TYPE_NONE = 0
+
+
+_RECORD_TYPE_FULL = 1
+
+
+_RECORD_TYPE_FIRST = 2
+
+
+_RECORD_TYPE_MIDDLE = 3
+
+
+_RECORD_TYPE_LAST = 4
+
+
+
+_CRC_MASK_DELTA = 0xa282ead8
+
+
+def _mask_crc(crc):
+  """Mask crc.
+
+  Args:
+    crc: integer crc.
+  Returns:
+    masked integer crc.
+  """
+  return (((crc >> 15) | (crc << 17)) + _CRC_MASK_DELTA) & 0xFFFFFFFFL
+
+
+def _unmask_crc(masked_crc):
+  """Unmask crc.
+
+  Args:
+    masked_crc: masked integer crc.
+  Retruns:
+    orignal crc.
+  """
+  rot = (masked_crc - _CRC_MASK_DELTA) & 0xFFFFFFFFL
+  return ((rot >> 17) | (rot << 15)) & 0xFFFFFFFFL
+
+
+class RecordsWriter(object):
+  """A writer for records format."""
+
+  def __init__(self, writer):
+    """Constructor.
+
+    Args:
+      writer: a writer conforming to Python io.RawIOBase interface that
+        implements 'write'.
+    """
+    self.__writer = writer
+    self.__position = 0
+
+  def __write_record(self, record_type, data):
+    """Write single physical record."""
+    length = len(data)
+
+    crc = crc32c.crc_update(crc32c.CRC_INIT, [record_type])
+    crc = crc32c.crc_update(crc, data)
+    crc = crc32c.crc_finalize(crc)
+
+    self.__writer.write(
+        struct.pack(_HEADER_FORMAT, _mask_crc(crc), length, record_type))
+    self.__writer.write(data)
+    self.__position += _HEADER_LENGTH + length
+
+  def write(self, data):
+    """Write single record.
+
+    Args:
+      data: record data to write as string, byte array or byte sequence.
+    """
+    block_remaining = _BLOCK_SIZE - self.__position % _BLOCK_SIZE
+
+    if block_remaining < _HEADER_LENGTH:
+
+      self.__writer.write('\x00' * block_remaining)
+      self.__position += block_remaining
+      block_remaining = _BLOCK_SIZE
+
+    if block_remaining < len(data) + _HEADER_LENGTH:
+      first_chunk = data[:block_remaining - _HEADER_LENGTH]
+      self.__write_record(_RECORD_TYPE_FIRST, first_chunk)
+      data = data[len(first_chunk):]
+
+      while True:
+        block_remaining = _BLOCK_SIZE - self.__position % _BLOCK_SIZE
+        if block_remaining >= len(data) + _HEADER_LENGTH:
+          self.__write_record(_RECORD_TYPE_LAST, data)
+          break
+        else:
+          chunk = data[:block_remaining - _HEADER_LENGTH]
+          self.__write_record(_RECORD_TYPE_MIDDLE, chunk)
+          data = data[len(chunk):]
+    else:
+      self.__write_record(_RECORD_TYPE_FULL, data)
+
+  def __enter__(self):
+    return self
+
+  def __exit__(self, atype, value, traceback):
+    self.close()
+
+  def close(self):
+    pass
+
+  def _pad_block(self):
+    """Pad block with 0.
+
+    Pad current block with 0. Reader will simply treat these as corrupted
+    record and skip the block.
+
+    This method is idempotent.
+    """
+    pad_length = _BLOCK_SIZE - self.__position % _BLOCK_SIZE
+    if pad_length and pad_length != _BLOCK_SIZE:
+      self.__writer.write('\x00' * pad_length)
+      self.__position += pad_length
+
+
+class RecordsReader(object):
+  """A reader for records format."""
+
+  def __init__(self, reader):
+    """Init.
+
+    Args:
+      reader: a reader conforming to Python io.RawIOBase interface that
+        implements 'read', 'seek', and 'tell'.
+    """
+    self.__reader = reader
+
+  def __try_read_record(self):
+    """Try reading a record.
+
+    Returns:
+      (data, record_type) tuple.
+    Raises:
+      EOFError: when end of file was reached.
+      InvalidRecordError: when valid record could not be read.
+    """
+    block_remaining = _BLOCK_SIZE - self.__reader.tell() % _BLOCK_SIZE
+    if block_remaining < _HEADER_LENGTH:
+      return ('', _RECORD_TYPE_NONE)
+
+    header = self.__reader.read(_HEADER_LENGTH)
+    if len(header) != _HEADER_LENGTH:
+      raise EOFError('Read %s bytes instead of %s' %
+                     (len(header), _HEADER_LENGTH))
+
+    (masked_crc, length, record_type) = struct.unpack(_HEADER_FORMAT, header)
+    crc = _unmask_crc(masked_crc)
+
+    if length + _HEADER_LENGTH > block_remaining:
+
+      raise errors.InvalidRecordError('Length is too big')
+
+    data = self.__reader.read(length)
+    if len(data) != length:
+      raise EOFError('Not enough data read. Expected: %s but got %s' %
+                     (length, len(data)))
+
+    if record_type == _RECORD_TYPE_NONE:
+      return ('', record_type)
+
+    actual_crc = crc32c.crc_update(crc32c.CRC_INIT, [record_type])
+    actual_crc = crc32c.crc_update(actual_crc, data)
+    actual_crc = crc32c.crc_finalize(actual_crc)
+
+    if actual_crc != crc:
+      raise errors.InvalidRecordError('Data crc does not match')
+    return (data, record_type)
+
+  def __sync(self):
+    """Skip reader to the block boundary."""
+    pad_length = _BLOCK_SIZE - self.__reader.tell() % _BLOCK_SIZE
+    if pad_length and pad_length != _BLOCK_SIZE:
+      data = self.__reader.read(pad_length)
+      if len(data) != pad_length:
+        raise EOFError('Read %d bytes instead of %d' %
+                       (len(data), pad_length))
+
+  def read(self):
+    """Reads record from current position in reader."""
+    data = None
+    while True:
+      last_offset = self.tell()
+      try:
+        (chunk, record_type) = self.__try_read_record()
+        if record_type == _RECORD_TYPE_NONE:
+          self.__sync()
+        elif record_type == _RECORD_TYPE_FULL:
+          if data is not None:
+            logging.warning(
+                "Ordering corruption: Got FULL record while already "
+                "in a chunk at offset %d", last_offset)
+          return chunk
+        elif record_type == _RECORD_TYPE_FIRST:
+          if data is not None:
+            logging.warning(
+                "Ordering corruption: Got FIRST record while already "
+                "in a chunk at offset %d", last_offset)
+          data = chunk
+        elif record_type == _RECORD_TYPE_MIDDLE:
+          if data is None:
+            logging.warning(
+                "Ordering corruption: Got MIDDLE record before FIRST "
+                "record at offset %d", last_offset)
+          else:
+            data += chunk
+        elif record_type == _RECORD_TYPE_LAST:
+          if data is None:
+            logging.warning(
+                "Ordering corruption: Got LAST record but no chunk is in "
+                "progress at offset %d", last_offset)
+          else:
+            result = data + chunk
+            data = None
+            return result
+        else:
+          raise errors.InvalidRecordError(
+              "Unsupported record type: %s" % record_type)
+
+      except errors.InvalidRecordError, e:
+        logging.warning("Invalid record encountered at %s (%s). Syncing to "
+                        "the next block", last_offset, e)
+        data = None
+        self.__sync()
+
+  def __iter__(self):
+    try:
+      while True:
+        yield self.read()
+    except EOFError:
+      pass
+
+  def tell(self):
+    """Return file's current position."""
+    return self.__reader.tell()
+
+  def seek(self, *args, **kwargs):
+    """Set the file's current position.
+
+    Arguments are passed directly to the underlying reader.
+    """
+    return self.__reader.seek(*args, **kwargs)
diff --git a/google/appengine/ext/mapreduce/shuffler.py b/google/appengine/ext/mapreduce/shuffler.py
index 69023a6..f9aea9e 100644
--- a/google/appengine/ext/mapreduce/shuffler.py
+++ b/google/appengine/ext/mapreduce/shuffler.py
@@ -50,7 +50,6 @@
 from appengine_pipeline.src.pipeline import common as pipeline_common
 from google.appengine.api import files
 from google.appengine.api.files import file_service_pb
-from google.appengine.api.files import records
 from google.appengine.ext import db
 from google.appengine.ext.mapreduce import context
 from google.appengine.ext.mapreduce import errors
@@ -59,6 +58,7 @@
 from google.appengine.ext.mapreduce import operation
 from google.appengine.ext.mapreduce import output_writers
 from google.appengine.ext.mapreduce import pipeline_base
+from google.appengine.ext.mapreduce import records
 
 
 
diff --git a/google/appengine/ext/remote_api/handler.py b/google/appengine/ext/remote_api/handler.py
index eedcba3..376adc7 100644
--- a/google/appengine/ext/remote_api/handler.py
+++ b/google/appengine/ext/remote_api/handler.py
@@ -383,6 +383,9 @@
     except Exception, e:
       logging.exception('Exception while handling %s', request)
       self.response.set_status(200)
+
+
+
       response.set_exception(pickle.dumps(e))
       if isinstance(e, apiproxy_errors.ApplicationError):
         application_error = response.mutable_application_error()
diff --git a/google/appengine/ext/webapp/mock_webapp.py b/google/appengine/ext/webapp/mock_webapp.py
index f6a9491..91effd8 100644
--- a/google/appengine/ext/webapp/mock_webapp.py
+++ b/google/appengine/ext/webapp/mock_webapp.py
@@ -62,6 +62,7 @@
     self.query_string = ''
     self.update_properties()
     self.environ = {}
+    self.remote_addr = '10.0.0.1'
 
   def get_path(self):
     return self._path
diff --git a/google/appengine/tools/app_engine_config_exception.py b/google/appengine/tools/app_engine_config_exception.py
new file mode 100644
index 0000000..2275ddd
--- /dev/null
+++ b/google/appengine/tools/app_engine_config_exception.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Contains exception class for reporting XML parsing errors."""
+
+
+class AppEngineConfigException(Exception):
+  """generic exception class for App Engine application configuration."""
+  pass
diff --git a/google/appengine/tools/app_engine_web_xml_parser.py b/google/appengine/tools/app_engine_web_xml_parser.py
new file mode 100644
index 0000000..89e9b51
--- /dev/null
+++ b/google/appengine/tools/app_engine_web_xml_parser.py
@@ -0,0 +1,558 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Directly processes text of appengine-web.xml.
+
+AppEngineWebXmlParser is called with XML string to produce an AppEngineWebXml
+object containing the data from that string.
+
+AppEngineWebXmlParser: converts xml to AppEngineWebXml object
+AppEngineWebXml: Contains relevant information from app_engine_web.xml
+
+Dummy Classes:
+ManualScaling
+BasicScaling
+UserPermission
+AdminConsolePage
+ErrorHandler
+ApiConfig
+PrioritySpecifierEntry
+StaticFileInclude
+AppEngineConfigException - generically reports illegal inputs.
+
+"""
+
+import os
+import re
+from xml.etree import ElementTree
+
+from google.appengine.tools import xml_parser_utils
+from google.appengine.tools.app_engine_config_exception import AppEngineConfigException
+from google.appengine.tools.basic_equality_mixin import BasicEqualityMixin
+
+
+class AppEngineWebXmlParser(object):
+  """Provides logic for walking down XML tree and pulling data."""
+
+  def ProcessXml(self, xml_str):
+    """Parses XML string and returns object representation of relevant info.
+
+    Uses ElementTree parser to return a tree representation of XML.
+    Then walks down that tree and extracts important info and adds it
+    to the object.
+
+    Args:
+      xml_str: The XML string itself
+
+    Returns:
+      If there is well-formed but illegal XML, returns a list of
+      errors. Otherwise, returns an AppEngineWebXml object containing
+      information from XML.
+
+    Raises:
+      AppEngineConfigException: In case of malformed XML or illegal inputs.
+    """
+    try:
+      self.app_engine_web_xml = AppEngineWebXml()
+      self.errors = []
+      xml_root = ElementTree.fromstring(xml_str)
+
+      for child in xml_root.getchildren():
+        self.ProcessChildNode(child)
+      self.CheckScalingConstraints()
+      if self.errors:
+
+        raise AppEngineConfigException('\n'.join(self.errors))
+      return self.app_engine_web_xml
+    except ElementTree.ParseError:
+      raise AppEngineConfigException('Bad input -- not valid XML')
+
+  def ProcessChildNode(self, child_node):
+    """Processes second-level nodes one by one.
+
+    According to the tag of the node passed in, processes it a certain way.
+
+    Args:
+      child_node: a "second-level" node in the appengine-web.xml tree
+
+    Raises:
+      AppEngineConfigException - in case tag is not recognized.
+    """
+
+    element_name = xml_parser_utils.GetTag(child_node)
+    camel_case_name = ''.join(part.title() for part in element_name.split('-'))
+    method_name = 'Process%sNode' % camel_case_name
+    if hasattr(self, method_name) and method_name is not 'ProcessChildNode':
+      getattr(self, method_name)(child_node)
+    else:
+      self.errors.append('Second-level tag not recognized: <%s>' % element_name)
+
+  def ProcessSystemPropertiesNode(self, node):
+    for sub_node in xml_parser_utils.GetNodes(node, 'property'):
+      prop_name = xml_parser_utils.GetAttribute(sub_node, 'name')
+      prop_value = xml_parser_utils.GetAttribute(sub_node, 'value')
+      self.app_engine_web_xml.system_properties[prop_name] = prop_value
+
+  def ProcessVmSettingsNode(self, node):
+    for sub_node in xml_parser_utils.GetNodes(node, 'setting'):
+      prop_name = xml_parser_utils.GetAttribute(sub_node, 'name')
+      prop_value = xml_parser_utils.GetAttribute(sub_node, 'value')
+      self.app_engine_web_xml.vm_settings[prop_name] = prop_value
+
+  def ProcessEnvVariablesNode(self, node):
+    for sub_node in xml_parser_utils.GetNodes(node, 'env-var'):
+      prop_name = xml_parser_utils.GetAttribute(sub_node, 'name')
+      prop_value = xml_parser_utils.GetAttribute(sub_node, 'value')
+      self.app_engine_web_xml.env_variables[prop_name] = prop_value
+
+  def ProcessApplicationNode(self, node):
+    self.app_engine_web_xml.app_id = node.text
+
+  def ProcessVersionNode(self, node):
+    self.app_engine_web_xml.version_id = node.text
+
+  def ProcessSourceLanguageNode(self, node):
+    self.app_engine_web_xml.source_language = node.text
+
+  def ProcessModuleNode(self, node):
+    self.app_engine_web_xml.module = node.text
+
+  def ProcessInstanceClassNode(self, node):
+    self.app_engine_web_xml.instance_class = node.text
+
+  def ProcessAutomaticScalingNode(self, node):
+    """Sets automatic scaling settings."""
+    automatic_scaling = AutomaticScaling()
+    automatic_scaling.min_pending_latency = xml_parser_utils.GetChildNodeText(
+        node, 'min-pending-latency').strip()
+    automatic_scaling.max_pending_latency = xml_parser_utils.GetChildNodeText(
+        node, 'max-pending-latency').strip()
+    automatic_scaling.min_idle_instances = xml_parser_utils.GetChildNodeText(
+        node, 'min-idle-instances').strip()
+    automatic_scaling.max_idle_instances = xml_parser_utils.GetChildNodeText(
+        node, 'max-idle-instances').strip()
+    self.app_engine_web_xml.automatic_scaling = automatic_scaling
+
+  def ProcessManualScalingNode(self, node):
+    manual_scaling = ManualScaling()
+    manual_scaling.instances = xml_parser_utils.GetChildNodeText(
+        node, 'instances').strip()
+    self.app_engine_web_xml.manual_scaling = manual_scaling
+
+  def ProcessBasicScalingNode(self, node):
+    basic_scaling = BasicScaling()
+    basic_scaling.max_instances = xml_parser_utils.GetChildNodeText(
+        node, 'max-instances').strip()
+    basic_scaling.idle_timeout = xml_parser_utils.GetChildNodeText(
+        node, 'idle-timeout').strip()
+    self.app_engine_web_xml.basic_scaling = basic_scaling
+
+  def ProcessStaticFilesNode(self, node):
+    """Processes files according to filetype."""
+    for sub_node in xml_parser_utils.GetNodes(node, 'include'):
+      path = xml_parser_utils.GetAttribute(sub_node, 'path').strip()
+      expiration = xml_parser_utils.GetAttribute(sub_node, 'expiration').strip()
+      static_file_include = StaticFileInclude()
+      static_file_include.pattern = path
+      static_file_include.expiration = expiration
+      static_file_include.http_headers = {}
+
+      for http_header_node in xml_parser_utils.GetNodes(
+          sub_node, 'http-header'):
+        name = xml_parser_utils.GetAttribute(http_header_node, 'name')
+        value = xml_parser_utils.GetAttribute(http_header_node, 'value')
+
+        if name in static_file_include.http_headers:
+          self.errors.append('Headers can only be entered once; %s entered '
+                             'more than once' % name)
+        static_file_include.http_headers[name] = value
+      self.app_engine_web_xml.static_file_includes.append(static_file_include)
+
+    for sub_node in xml_parser_utils.GetNodes(node, 'exclude'):
+      path = xml_parser_utils.GetAttribute(sub_node, 'path').strip()
+      self.app_engine_web_xml.static_file_excludes.append(path)
+
+  def ProcessResourceFilesNode(self, node):
+    for sub_node in xml_parser_utils.GetNodes(node, 'include'):
+      path = xml_parser_utils.GetAttribute(sub_node, 'path').strip()
+      self.app_engine_web_xml.resource_file_includes.append(path)
+
+    for sub_node in xml_parser_utils.GetNodes(node, 'exclude'):
+      path = xml_parser_utils.GetAttribute(sub_node, 'path').strip()
+      self.app_engine_web_xml.resource_file_excludes.append(path)
+
+  def ProcessSslEnabledNode(self, node):
+    value = xml_parser_utils.BooleanValue(node.text)
+    self.app_engine_web_xml.ssl_enabled = value
+
+  def ProcessSessionsEnabledNode(self, node):
+    value = xml_parser_utils.BooleanValue(node.text)
+    self.app_engine_web_xml.sessions_enabled = value
+
+  def ProcessAsyncSessionPersistenceNode(self, node):
+    enabled = xml_parser_utils.BooleanValue(
+        xml_parser_utils.GetAttribute(node, 'enabled'))
+    self.app_engine_web_xml.async_session_persistence = enabled
+    queue_name = xml_parser_utils.GetAttribute(node, 'queue-name').strip()
+    self.app_engine_web_xml.async_session_persistence_queue_name = queue_name
+
+  def ProcessUserPermissionsNode(self, node):
+    for node in xml_parser_utils.GetNodes(node, 'permission'):
+      class_name = xml_parser_utils.GetAttribute(node, 'class-name').strip()
+      name = xml_parser_utils.GetAttribute(node, 'name').strip()
+      actions = xml_parser_utils.GetAttribute(node, 'actions').strip()
+      if class_name.startswith('java.'):
+        self.errors.append('Cannot specify user-permission for '
+                           'classes in java.* packages.')
+      user_permission = UserPermission()
+      user_permission.class_name = class_name
+      user_permission.name = name
+      user_permission.actions = actions
+      self.app_engine_web_xml.user_permissions.append(user_permission)
+
+  def ProcessPublicRootNode(self, node):
+    """Sets public root node so that it is of form "/foo"."""
+    new_root = node.text
+    if new_root:
+      if '*' in new_root:
+        self.errors.append('public-root cannot contain wildcards')
+        return
+      if new_root.endswith('/'):
+        new_root = new_root[:-1]
+      if not new_root.startswith('/'):
+        new_root = '/' + new_root
+
+      self.app_engine_web_xml.public_root = new_root
+
+  def ProcessInboundServicesNode(self, node):
+    for node in xml_parser_utils.GetNodes(node, 'service'):
+      self.app_engine_web_xml.inbound_services.add(node.text)
+
+  def ProcessPrecompilationEnabledNode(self, node):
+    value = xml_parser_utils.BooleanValue(node.text)
+    self.app_engine_web_xml.precompilation_enabled = value
+
+  def ProcessAdminConsoleNode(self, node):
+    for node in xml_parser_utils.GetNodes(node, 'page'):
+      name = xml_parser_utils.GetAttribute(node, 'name').strip()
+      url = xml_parser_utils.GetAttribute(node, 'url').strip()
+      admin_console_page = AdminConsolePage()
+      admin_console_page.name = name
+      admin_console_page.url = url
+      self.app_engine_web_xml.admin_console_pages.append(admin_console_page)
+
+  def ProcessStaticErrorHandlersNode(self, node):
+    for node in xml_parser_utils.GetNodes(node, 'handler'):
+      filename = xml_parser_utils.GetAttribute(node, 'file').strip()
+      error_code = xml_parser_utils.GetAttribute(node, 'error-code').strip()
+      error_handler = ErrorHandler()
+      error_handler.name = filename
+      error_handler.code = error_code
+      self.app_engine_web_xml.static_error_handlers.append(error_handler)
+
+  def ProcessWarmupRequestsEnabledNode(self, node):
+    warmup_requests_enabled = xml_parser_utils.BooleanValue(node.text)
+    if warmup_requests_enabled:
+      self.inbound_services.add(self.WARMUP_SERVICE)
+    else:
+      self.inbound_services.remove(self.WARMUP_SERVICE)
+
+  def ProcessThreadsafeNode(self, node):
+    value = xml_parser_utils.BooleanValue(node.text)
+    self.app_engine_web_xml.threadsafe = value
+    self.app_engine_web_xml.threadsafe_value_provided = True
+
+  def ProcessCodeLockNode(self, node):
+    self.app_engine_web_xml.codelock = xml_parser_utils.BooleanValue(node.text)
+
+  def ProcessUseVmNode(self, node):
+    self.app_engine_web_xml.use_vm = xml_parser_utils.BooleanValue(node.text)
+
+  def ProcessApiConfigNode(self, node):
+    servlet = xml_parser_utils.GetAttribute(node, 'servlet-class').strip()
+    url = xml_parser_utils.GetAttribute(node, 'url-pattern').strip()
+    api_config = ApiConfig()
+    api_config.servlet_class = servlet
+    api_config.url = url
+    self.app_engine_web_xml.api_config = api_config
+    for sub_node in xml_parser_utils.GetNodes(
+        node, 'endpoint-servlet-mapping-id'):
+      api_id = sub_node.text.strip()
+      if api_id:
+        self.app_engine_web_xml.api_endpoint_ids.append(api_id)
+
+  def ProcessPagespeedNode(self, node):
+    """Processes URLs and puts them into the Pagespeed object."""
+    pagespeed = Pagespeed()
+    pagespeed.url_blacklist = [
+        sub_node.text for sub_node in xml_parser_utils.GetNodes(
+            node, 'url-blacklist')]
+    pagespeed.domains_to_rewrite = [
+        sub_node.text for sub_node in xml_parser_utils.GetNodes(
+            node, 'domain-to-rewrite')]
+    pagespeed.enabled_rewriters = [
+        sub_node.text for sub_node in xml_parser_utils.GetNodes(
+            node, 'enabled-rewriter')]
+    pagespeed.disabled_rewriters = [
+        sub_node.text for sub_node in xml_parser_utils.GetNodes(
+            node, 'disabled-rewriter')]
+    self.app_engine_web_xml.pagespeed = pagespeed
+
+  def ProcessClassLoaderConfig(self, node):
+    for node in xml_parser_utils.GetNodes(node, 'priority-specifier'):
+      entry = PrioritySpecifierEntry()
+      entry.filename = xml_parser_utils.GetAttribute(node, 'filename')
+      if not entry.filename:
+        self.errors.append('Filename needs to be provided for each '
+                           'priority specifier')
+      elif self.app_engine_web_xml.SpecifierEnteredAlready(entry.filename):
+        self.errors.append('Cannot have more than one priority specifier with '
+                           'the same filename: %s' % entry.filename)
+      else:
+        try:
+          priority = xml_parser_utils.GetAttribute(node, 'priority')
+          entry.priority = float(priority) if priority else 1.0
+        except ValueError:
+          self.errors.append('priority-specifiers must be numbers')
+        self.app_engine_web_xml.class_loader_config.append(entry)
+
+  def ProcessUrlStreamHandlerNode(self, node):
+    """Processes url stream handler, makes sure it is correct type."""
+    new_type = node.text
+    urlfetch = self.app_engine_web_xml.URL_HANDLER_URLFETCH
+    native = self.app_engine_web_xml.URL_HANDLER_NATIVE
+    if new_type not in (urlfetch, native):
+      exception_str = 'url-stream-handler must be %s or %s given %s' % (
+          urlfetch, native, new_type)
+      self.errors.append(exception_str)
+    self.app_engine_web_xml.url_stream_handler_type = new_type
+
+  def ProcessUseGoogleConnectorJNode(self, node):
+    value = xml_parser_utils.BooleanValue(node.text)
+    self.app_engine_web_xml.google_connector_j = value
+
+  def ProcessAutoIdPolicyNode(self, node):
+    policy = node.text
+    if policy:
+      default = self.app_engine_web_xml.DEFAULT_POLICY
+      legacy = self.app_engine_web_xml.LEGACY_POLICY
+      if policy not in (default, legacy):
+        self.errors.append('auto-id-policy must be either "%s" or '
+                           '"%s" given %s' % (default, legacy, policy))
+        return
+      self.app_engine_web_xml.auto_id_policy = policy
+
+  def CheckScalingConstraints(self):
+    """Checks that at most one type of scaling is enabled."""
+    scaling_num = sum([x is not None for x in [
+        self.app_engine_web_xml.basic_scaling,
+        self.app_engine_web_xml.automatic_scaling,
+        self.app_engine_web_xml.manual_scaling,
+        ]])
+    if scaling_num > 1:
+      self.errors.append('Cannot enable more than one type of scaling')
+
+
+class AppEngineWebXml(BasicEqualityMixin):
+  """Organizes and stores data from appengine-web.xml."""
+  URL_HANDLER_URLFETCH = 'urlfetch'
+  URL_HANDLER_NATIVE = 'native'
+  WARMUP_SERVICE = 'warmup'
+  DEFAULT_POLICY = 'default'
+  LEGACY_POLICY = 'legacy'
+
+  def __init__(self):
+    """Initializes an empty AppEngineWebXml object."""
+    self.app_id = None
+    self.version_id = None
+    self.source_language = None
+    self.module = None
+    self.system_properties = {}
+    self.vm_settings = {}
+    self.env_variables = {}
+    self.instance_class = None
+    self.automatic_scaling = None
+    self.manual_scaling = None
+    self.basic_scaling = None
+    self.ssl_enabled = True
+    self.sessions_enabled = False
+    self.async_session_persistence = False
+    self.async_session_persistence_queue_name = None
+    self.user_permissions = []
+    self.public_root = ''
+    self.static_include_pattern = None
+    self.inbound_services = set([self.WARMUP_SERVICE])
+    self.precompilation_enabled = True
+    self.admin_console_pages = []
+    self.static_error_handlers = []
+    self.threadsafe = False
+    self.threadsafe_value_provided = False
+    self.codelock = None
+    self.use_vm = False
+    self.api_config = None
+    self.api_endpoint_ids = []
+    self.pagespeed = None
+    self.class_loader_config = []
+    self.url_stream_handler_type = None
+    self.use_google_connector_j = None
+    self.static_file_includes = []
+    self.static_file_excludes = []
+    self.resource_file_includes = []
+    self.resource_file_excludes = []
+    self.auto_id_policy = self.DEFAULT_POLICY
+    self._app_root = ''
+    self.static_include_pattern = None
+    self.static_exclude_pattern = None
+    self.resource_include_pattern = None
+    self.resource_exclude_pattern = None
+
+  def SpecifierEnteredAlready(self, filename):
+    return filename in (entry.filename for entry in self.class_loader_config)
+
+  def IncludesStatic(self, path):
+    """Checks whether a given file should be classified as a static file."""
+    if not self.static_include_pattern:
+
+      includes_list = ([inc.pattern for inc in self.static_file_includes]
+                       or [os.path.join(self.public_root, '**')])
+      self.static_include_pattern = self._CreatePatternListRegex(includes_list)
+
+    if self.static_file_excludes:
+      if not self.static_exclude_pattern:
+        self.static_exclude_pattern = self._CreatePatternListRegex(
+            self.static_file_excludes)
+      if self.static_exclude_pattern.match(path):
+        return False
+
+    return self.static_include_pattern.match(path)
+
+  def IncludesResource(self, path):
+    """Checks whether a given file should be classified as a resource file."""
+    if not self.resource_include_pattern:
+      includes = self.resource_file_includes or ['**']
+      self.resource_include_pattern = self._CreatePatternListRegex(includes)
+
+    if self.resource_file_excludes:
+      if not self.resource_exclude_pattern:
+        self.resource_exclude_pattern = self._CreatePatternListRegex(
+            self.resource_file_excludes)
+      if self.resource_exclude_pattern.match(path):
+        return False
+
+    return self.resource_include_pattern.match(path)
+
+  def _CreatePatternListRegex(self, patterns):
+    """Converts a list of patterns into a regex.
+
+    Args:
+      patterns: A list of single and double wildcarded path specifying patterns.
+    Returns:
+      A regular expression matching any of the paths in patterns matching
+      one of the files with the path in the subdirectory of the application
+      basepath. Ex. if we have and basepath (app_root) of "approot" and
+      patterns "foo" and "bar", this returns the compiled regular expression
+      of '(^approot\\/foo$|^approot\\/bar$)'.
+    """
+    regexed_patterns = [self._CreateFileNameRegex(pat) for pat in patterns]
+    def _StripLeadingSlashes(pat):
+      while pat.startswith('/'):
+        pat = pat[1:]
+      return pat
+    regexed_patterns = [self._CreateFileNameRegex(_StripLeadingSlashes(pat))
+                        for pat in patterns]
+
+    app_root_regex = self._CreateFileNameRegex(self.app_root)
+    regexed_patterns = ['^%s\\/%s$' % (app_root_regex, pattern_regex)
+                        for pattern_regex in regexed_patterns]
+    return re.compile('(%s)' % '|'.join(regexed_patterns))
+
+  def _CreateFileNameRegex(self, filename):
+    """Converts the object's pattern into an unanchored regular expression.
+
+    Static file patterns can contain single- and double-wildcards. '**'
+    represents zero or more directories in a path, and '*' represents zero or
+    more characters in a file or directory name.
+
+    Args:
+      filename: a resource or static file pattern.
+    Returns:
+      regular expression version of the pattern. For example, a filename of
+      'foo/**.txt' becomes 'foo\\/.*\\.txt'.
+    """
+    return re.escape(filename).replace('\\*\\*', '.*').replace('\\*', '[^/]*')
+
+  def _SetAppRoot(self, new_root):
+    self._app_root = new_root
+
+
+    self.static_include_pattern = None
+    self.static_exclude_pattern = None
+    self.resource_include_pattern = None
+    self.resource_exclude_pattern = None
+
+  def _GetAppRoot(self):
+    return self._app_root
+
+  app_root = property(_GetAppRoot, _SetAppRoot)
+
+
+class AutomaticScaling(BasicEqualityMixin):
+  """Instances contain information about automatic scaling settings."""
+  pass
+
+
+class ManualScaling(BasicEqualityMixin):
+  """Instances contain information about manual scaling settings."""
+  pass
+
+
+class BasicScaling(BasicEqualityMixin):
+  """Instances contain information about basic scaling settings."""
+  pass
+
+
+class UserPermission(BasicEqualityMixin):
+  """Instances contain information about user permissions."""
+  pass
+
+
+class AdminConsolePage(BasicEqualityMixin):
+  """Instances contain information about the admin console page settings."""
+  pass
+
+
+class ErrorHandler(BasicEqualityMixin):
+  """Instances contain information about error handler settings."""
+  pass
+
+
+class ApiConfig(BasicEqualityMixin):
+  """Instances contain information about the API config settings."""
+  pass
+
+
+class Pagespeed(BasicEqualityMixin):
+  """Instances contain information about the pagespeed settings."""
+
+
+class PrioritySpecifierEntry(BasicEqualityMixin):
+  """Instances describe a priority specifier entry in appengine-web.xml."""
+  pass
+
+
+class StaticFileInclude(BasicEqualityMixin):
+  """Instances describe static files to be included in app configuration."""
+  pass
diff --git a/google/appengine/tools/appcfg.py b/google/appengine/tools/appcfg.py
index e05d575..6b8870a 100644
--- a/google/appengine/tools/appcfg.py
+++ b/google/appengine/tools/appcfg.py
@@ -30,6 +30,7 @@
 methods to add to the list of files, fetch a list of modified files, upload
 files, and commit or rollback the transaction.
 """
+from __future__ import with_statement
 
 
 import calendar
@@ -44,6 +45,7 @@
 import os
 import random
 import re
+import shutil
 import subprocess
 import sys
 import tempfile
@@ -67,7 +69,11 @@
 from google.appengine.api import yaml_errors
 from google.appengine.api import yaml_object
 from google.appengine.datastore import datastore_index
+from google.appengine.tools import app_engine_web_xml_parser
 from google.appengine.tools import appengine_rpc
+from google.appengine.tools import backends_xml_parser
+from google.appengine.tools import web_xml_parser
+from google.appengine.tools import yaml_translator
 try:
 
 
@@ -82,10 +88,10 @@
 TUPLE_DELIMITER = '|'
 BACKENDS_ACTION = 'backends'
 BACKENDS_MESSAGE = ('Looks like you\'re using Backends. We suggest that you '
-                    'make the switch to App Engine Modules. See the Modules '
-                    'documentation to learn more about converting: '
-                    'https://developers.google.com/appengine/docs/python/'
-                    'modules/converting')
+                    'start looking at App Engine Modules. See the Modules '
+                    'documentation to learn more about converting: ')
+_CONVERTING_URL = (
+    'https://developers.google.com/appengine/docs/%s/modules/converting')
 
 
 MAX_LOG_LEVEL = 4
@@ -113,7 +119,7 @@
 DAY = 24*3600
 SUNDAY = 6
 
-SUPPORTED_RUNTIMES = ('go', 'php', 'python', 'python27')
+SUPPORTED_RUNTIMES = ('go', 'php', 'python', 'python27', 'java', 'java7')
 
 
 
@@ -175,6 +181,21 @@
   PrintUpdate(msg)
 
 
+def BackendsStatusUpdate(runtime):
+  """Print the Backends status message based on current runtime.
+
+  Args:
+    runtime: String name of current runtime.
+  """
+  language = runtime
+  if language == 'python27':
+    language = 'python'
+  elif language == 'java7':
+    language = 'java'
+  if language == 'python' or language == 'java':
+    StatusUpdate(BACKENDS_MESSAGE + (_CONVERTING_URL % language))
+
+
 def ErrorUpdate(msg):
   """Print an error message to stderr."""
   PrintUpdate(msg)
@@ -485,75 +506,79 @@
 class CronEntryUpload(object):
   """Provides facilities to upload cron entries to the hosting service."""
 
-  def __init__(self, rpcserver, config, cron):
+  def __init__(self, rpcserver, cron):
     """Creates a new CronEntryUpload.
 
     Args:
       rpcserver: The RPC server to use.  Should be an instance of a subclass of
       AbstractRpcServer
-      config: The AppInfoExternal object derived from the app.yaml file.
       cron: The CronInfoExternal object loaded from the cron.yaml file.
     """
     self.rpcserver = rpcserver
-    self.config = config
     self.cron = cron
 
   def DoUpload(self):
     """Uploads the cron entries."""
+
+
+    app_id = self.cron.application
+    self.cron.application = None
+
     StatusUpdate('Uploading cron entries.')
     self.rpcserver.Send('/api/cron/update',
-                        app_id=self.config.application,
-                        version=self.config.version,
+                        app_id=app_id,
                         payload=self.cron.ToYAML())
 
 
 class QueueEntryUpload(object):
   """Provides facilities to upload task queue entries to the hosting service."""
 
-  def __init__(self, rpcserver, config, queue):
+  def __init__(self, rpcserver, queue):
     """Creates a new QueueEntryUpload.
 
     Args:
       rpcserver: The RPC server to use.  Should be an instance of a subclass of
       AbstractRpcServer
-      config: The AppInfoExternal object derived from the app.yaml file.
       queue: The QueueInfoExternal object loaded from the queue.yaml file.
     """
     self.rpcserver = rpcserver
-    self.config = config
     self.queue = queue
 
   def DoUpload(self):
     """Uploads the task queue entries."""
+
+
+    app_id = self.queue.application
+    self.queue.application = None
     StatusUpdate('Uploading task queue entries.')
     self.rpcserver.Send('/api/queue/update',
-                        app_id=self.config.application,
-                        version=self.config.version,
+                        app_id=app_id,
                         payload=self.queue.ToYAML())
 
 
 class DosEntryUpload(object):
   """Provides facilities to upload dos entries to the hosting service."""
 
-  def __init__(self, rpcserver, config, dos):
+  def __init__(self, rpcserver, dos):
     """Creates a new DosEntryUpload.
 
     Args:
       rpcserver: The RPC server to use. Should be an instance of a subclass of
         AbstractRpcServer.
-      config: The AppInfoExternal object derived from the app.yaml file.
       dos: The DosInfoExternal object loaded from the dos.yaml file.
     """
     self.rpcserver = rpcserver
-    self.config = config
     self.dos = dos
 
   def DoUpload(self):
     """Uploads the dos entries."""
+
+
+    app_id = self.dos.application
+    self.dos.application = None
     StatusUpdate('Uploading DOS entries.')
     self.rpcserver.Send('/api/dos/update',
-                        app_id=self.config.application,
-                        version=self.config.version,
+                        app_id=app_id,
                         payload=self.dos.ToYAML())
 
 
@@ -620,8 +645,26 @@
   def SetVersion(self):
     """Sets the default version."""
     if self.module:
-      StatusUpdate('Setting default version of module %s of application %s '
-                   'to %s.' % (self.app_id, self.module, self.version))
+
+      modules = self.module.split(',')
+      if len(modules) > 1:
+        StatusUpdate('Setting the default version of modules %s of application '
+                     '%s to %s.' % (', '.join(modules),
+                                    self.app_id,
+                                    self.version))
+
+
+
+
+        params = [('app_id', self.app_id), ('version', self.version)]
+        params.extend(('module', module) for module in modules)
+        url = '/api/appversion/setdefault?' + urllib.urlencode(sorted(params))
+        self.rpcserver.Send(url)
+        return
+
+      else:
+        StatusUpdate('Setting default version of module %s of application %s '
+                     'to %s.' % (self.module, self.app_id, self.version))
     else:
       StatusUpdate('Setting default version of application %s to %s.'
                    % (self.app_id, self.version))
@@ -1491,7 +1534,8 @@
 
   def __init__(self, rpcserver, config, module_yaml_path='app.yaml',
                backend=None,
-               error_fh=None):
+               error_fh=None,
+               get_version=sdk_update_checker.GetVersionObject):
     """Creates a new AppVersionUpload.
 
     Args:
@@ -1504,6 +1548,8 @@
       backend: If specified, indicates the update applies to the given backend.
         The backend name must match an entry in the backends: stanza.
       error_fh: Unexpected HTTPErrors are printed to this file handle.
+      get_version: Method for determining the current SDK version. The override
+        is used for testing.
     """
     self.rpcserver = rpcserver
     self.config = config
@@ -1545,6 +1591,10 @@
       self.config.vm_settings = appinfo.VmSettings()
     self.config.vm_settings['module_yaml_path'] = module_yaml_path
 
+    if not self.config.vm_settings.get('image'):
+      sdk_version = get_version()
+      if sdk_version and sdk_version.get('release'):
+        self.config.vm_settings['image'] = sdk_version['release']
 
     if not self.config.auto_id_policy:
       self.config.auto_id_policy = appinfo.DATASTORE_ID_POLICY_DEFAULT
@@ -1741,7 +1791,7 @@
       self.file_batcher.AddToBatch(path, payload, None)
 
   def Precompile(self):
-    """Handle bytecode precompilation."""
+    """Handle precompilation."""
 
     StatusUpdate('Compilation starting.')
 
@@ -1791,11 +1841,11 @@
       otherwise.
 
     Raises:
-      Exception: Some required files were not uploaded.
+      RuntimeError: Some required files were not uploaded.
     """
     assert self.in_transaction, 'Begin() must be called before Commit().'
     if self.files:
-      raise Exception('Not all required files have been uploaded.')
+      raise RuntimeError('Not all required files have been uploaded.')
 
     def PrintRetryMessage(_, delay):
       StatusUpdate('Will check again in %s seconds.' % delay)
@@ -1810,7 +1860,7 @@
     if not success:
 
       logging.warning('Version still not ready to serve, aborting.')
-      raise Exception('Version not ready.')
+      raise RuntimeError('Version not ready.')
 
     result = self.StartServing()
     if not result:
@@ -1826,7 +1876,7 @@
       if not success:
 
         logging.warning('Version still not serving, aborting.')
-        raise Exception('Version not ready.')
+        raise RuntimeError('Version not ready.')
 
 
 
@@ -1838,7 +1888,7 @@
         if not success:
           logging.warning('Failed to update Endpoints configuration.  Try '
                           'updating again.')
-          raise Exception('Endpoints config update failed.')
+          raise RuntimeError('Endpoints config update failed.')
       self.in_transaction = False
 
     return app_summary
@@ -1854,11 +1904,11 @@
       otherwise.
 
     Raises:
-      Exception: Some required files were not uploaded.
+      RuntimeError: Some required files were not uploaded.
     """
     assert self.in_transaction, 'Begin() must be called before Deploy().'
     if self.files:
-      raise Exception('Not all required files have been uploaded.')
+      raise RuntimeError('Not all required files have been uploaded.')
 
     StatusUpdate('Starting deployment.')
     result = self.Send('/api/appversion/deploy')
@@ -1873,7 +1923,7 @@
     """Check if the new app version is ready to serve traffic.
 
     Raises:
-      Exception: Deploy has not yet been called.
+      RuntimeError: Deploy has not yet been called.
 
     Returns:
       True if the server returned the app is ready to serve.
@@ -1888,7 +1938,7 @@
     """Start serving with the newly created version.
 
     Raises:
-      Exception: Deploy has not yet been called.
+      RuntimeError: Deploy has not yet been called.
 
     Returns:
       The response body, as a string.
@@ -1917,7 +1967,7 @@
     """Check if the new app version is serving.
 
     Raises:
-      Exception: Deploy has not yet been called.
+      RuntimeError: Deploy has not yet been called.
 
     Returns:
       (serving, response) Where serving is True if the deployed app version is
@@ -2382,8 +2432,6 @@
 
 
     if action == BACKENDS_ACTION:
-
-      StatusUpdate(BACKENDS_MESSAGE)
       if len(self.args) < 1:
         RaiseParseError(action, self.actions[BACKENDS_ACTION])
 
@@ -2827,27 +2875,71 @@
     return self._ParseYamlFile(basepath, 'index',
                                datastore_index.ParseIndexDefinitions)
 
-  def _ParseCronYaml(self, basepath):
+  def _SetApplication(self, dest_yaml, basename, appyaml=None):
+    """Parses and sets the application property onto the dest_yaml parameter.
+
+    The order of precendence is:
+    1. Command line (-A application)
+    2. Specified dest_yaml file
+    3. App.yaml file
+
+    This exits with a parse error if application is not present in any of these
+    locations.
+
+    Args:
+      dest_yaml: The yaml object to set 'application' on.
+      basename: The name of the dest_yaml file for use in errors.
+      appyaml: The already parsed appyaml, if present. If none, this method will
+          attempt to parse app.yaml.
+    """
+    if self.options.app_id:
+      dest_yaml.application = self.options.app_id
+    if not dest_yaml.application:
+      if not appyaml:
+        appyaml = self._ParseYamlFile(self.basepath,
+                                      'app',
+                                      appinfo_includes.Parse)
+      if appyaml:
+        dest_yaml.application = appyaml.application
+      else:
+        self.parser.error('Expected -A app_id when %s.yaml.application is not '
+                          'set and app.yaml is not present.' % basename)
+
+  def _ParseCronYaml(self, basepath, appyaml=None):
     """Parses the cron.yaml file.
 
     Args:
       basepath: the directory of the application.
+      appyaml: The app.yaml, if present.
 
     Returns:
       A CronInfoExternal object or None if the file does not exist.
     """
-    return self._ParseYamlFile(basepath, 'cron', croninfo.LoadSingleCron)
+    cron_yaml = self._ParseYamlFile(basepath, 'cron', croninfo.LoadSingleCron)
+    if not cron_yaml:
+      return None
+    self._SetApplication(cron_yaml, 'cron', appyaml)
 
-  def _ParseQueueYaml(self, basepath):
+    return cron_yaml
+
+  def _ParseQueueYaml(self, basepath, appyaml=None):
     """Parses the queue.yaml file.
 
     Args:
       basepath: the directory of the application.
+      appyaml: The app.yaml, if present.
 
     Returns:
       A QueueInfoExternal object or None if the file does not exist.
     """
-    return self._ParseYamlFile(basepath, 'queue', queueinfo.LoadSingleQueue)
+    queue_yaml = self._ParseYamlFile(basepath,
+                                     'queue',
+                                     queueinfo.LoadSingleQueue)
+    if not queue_yaml:
+      return None
+
+    self._SetApplication(queue_yaml, 'queue', appyaml)
+    return queue_yaml
 
   def _ParseDispatchYaml(self, basepath):
     """Parses the dispatch.yaml file.
@@ -2861,16 +2953,22 @@
     return self._ParseYamlFile(basepath, 'dispatch',
                                dispatchinfo.LoadSingleDispatch)
 
-  def _ParseDosYaml(self, basepath):
+  def _ParseDosYaml(self, basepath, appyaml=None):
     """Parses the dos.yaml file.
 
     Args:
       basepath: the directory of the application.
+      appyaml: The app.yaml, if present.
 
     Returns:
       A DosInfoExternal object or None if the file does not exist.
     """
-    return self._ParseYamlFile(basepath, 'dos', dosinfo.LoadSingleDos)
+    dos_yaml = self._ParseYamlFile(basepath, 'dos', dosinfo.LoadSingleDos)
+    if not dos_yaml:
+      return None
+
+    self._SetApplication(dos_yaml, 'dos', appyaml)
+    return dos_yaml
 
   def Help(self, action=None):
     """Prints help for a specific action.
@@ -2940,6 +3038,11 @@
       otherwise.
     """
 
+    if not self.options.precompilation and appyaml.runtime == 'go':
+      logging.warning('Precompilation is required for Go apps; '
+                      'ignoring --no_precompilation')
+      self.options.precompilation = True
+
     if self.options.precompilation:
       if not appyaml.derived_file_type:
         appyaml.derived_file_type = []
@@ -2960,8 +3063,8 @@
         go_files = [f for f in app_paths
                     if f.endswith('.go') and not appyaml.nobuild_files.match(f)]
         if not go_files:
-          raise Exception('no Go source files to upload '
-                          '(-nobuild_files applied)')
+          raise RuntimeError('no Go source files to upload '
+                             '(-nobuild_files applied)')
         gab_argv = [
             os.path.join(goroot, 'bin', 'go-app-builder'),
             '-app_base', self.basepath,
@@ -2975,9 +3078,9 @@
                                stderr=subprocess.PIPE, env={})
           rc = p.wait()
         except Exception, e:
-          raise Exception('failed running go-app-builder', e)
+          raise RuntimeError('failed running go-app-builder', e)
         if rc != 0:
-          raise Exception(p.stderr.read())
+          raise RuntimeError(p.stderr.read())
 
 
 
@@ -3048,6 +3151,24 @@
     else:
       updatecheck = self.update_check_class(rpcserver, appyaml)
       updatecheck.CheckForUpdates()
+
+    def _AbortAppMismatch(yaml_name):
+      StatusUpdate('Error: Aborting upload because application in %s does not '
+                   'match application in app.yaml' % yaml_name)
+
+
+    dos_yaml = self._ParseDosYaml(self.basepath, appyaml)
+    if dos_yaml and dos_yaml.application != appyaml.application:
+      return _AbortAppMismatch('dos.yaml')
+
+    queue_yaml = self._ParseQueueYaml(self.basepath, appyaml)
+    if queue_yaml and queue_yaml.application != appyaml.application:
+      return _AbortAppMismatch('queue.yaml')
+
+    cron_yaml = self._ParseCronYaml(self.basepath, appyaml)
+    if cron_yaml and cron_yaml.application != appyaml.application:
+      return _AbortAppMismatch('cron.yaml')
+
     self.UpdateVersion(rpcserver, self.basepath, appyaml, yaml_file_basename)
 
     if appyaml.runtime == 'python':
@@ -3076,21 +3197,18 @@
             'indexes. Please retry later with appcfg.py update_indexes.')
 
 
-    cron_yaml = self._ParseCronYaml(self.basepath)
     if cron_yaml:
-      cron_upload = CronEntryUpload(rpcserver, appyaml, cron_yaml)
+      cron_upload = CronEntryUpload(rpcserver, cron_yaml)
       cron_upload.DoUpload()
 
 
-    queue_yaml = self._ParseQueueYaml(self.basepath)
     if queue_yaml:
-      queue_upload = QueueEntryUpload(rpcserver, appyaml, queue_yaml)
+      queue_upload = QueueEntryUpload(rpcserver, queue_yaml)
       queue_upload.DoUpload()
 
 
-    dos_yaml = self._ParseDosYaml(self.basepath)
     if dos_yaml:
-      dos_upload = DosEntryUpload(rpcserver, appyaml, dos_yaml)
+      dos_upload = DosEntryUpload(rpcserver, dos_yaml)
       dos_upload.DoUpload()
 
 
@@ -3115,7 +3233,8 @@
     """
     parser.add_option('--no_precompilation', action='store_false',
                       dest='precompilation', default=True,
-                      help='Disable automatic Python precompilation.')
+                      help='Disable automatic precompilation '
+                      '(ignored for Go apps).')
     parser.add_option('--backends', action='store_true',
                       dest='backends', default=False,
                       help='Update backends when performing appcfg update.')
@@ -3153,13 +3272,12 @@
     if self.args:
       self.parser.error('Expected a single <directory> argument.')
 
-    appyaml = self._ParseAppInfoFromYaml(self.basepath)
     rpcserver = self._GetRpcServer()
 
 
     cron_yaml = self._ParseCronYaml(self.basepath)
     if cron_yaml:
-      cron_upload = CronEntryUpload(rpcserver, appyaml, cron_yaml)
+      cron_upload = CronEntryUpload(rpcserver, cron_yaml)
       cron_upload.DoUpload()
     else:
       print >>sys.stderr, 'Could not find cron configuration. No action taken.'
@@ -3185,14 +3303,12 @@
     """Updates any new or changed task queue definitions."""
     if self.args:
       self.parser.error('Expected a single <directory> argument.')
-
-    appyaml = self._ParseAppInfoFromYaml(self.basepath)
     rpcserver = self._GetRpcServer()
 
 
     queue_yaml = self._ParseQueueYaml(self.basepath)
     if queue_yaml:
-      queue_upload = QueueEntryUpload(rpcserver, appyaml, queue_yaml)
+      queue_upload = QueueEntryUpload(rpcserver, queue_yaml)
       queue_upload.DoUpload()
     else:
       print >>sys.stderr, 'Could not find queue configuration. No action taken.'
@@ -3224,14 +3340,12 @@
     """Updates any new or changed dos definitions."""
     if self.args:
       self.parser.error('Expected a single <directory> argument.')
-
-    appyaml = self._ParseAppInfoFromYaml(self.basepath)
     rpcserver = self._GetRpcServer()
 
 
     dos_yaml = self._ParseDosYaml(self.basepath)
     if dos_yaml:
-      dos_upload = DosEntryUpload(rpcserver, appyaml, dos_yaml)
+      dos_upload = DosEntryUpload(rpcserver, dos_yaml)
       dos_upload.DoUpload()
     else:
       print >>sys.stderr, 'Could not find dos configuration. No action taken.'
@@ -3303,6 +3417,7 @@
     yaml_file_basename = 'app'
     appyaml = self._ParseAppInfoFromYaml(self.basepath,
                                          basename=yaml_file_basename)
+    BackendsStatusUpdate(appyaml.runtime)
     self.BackendsPhpCheck(appyaml)
     rpcserver = self._GetRpcServer()
 
@@ -3320,6 +3435,7 @@
 
 
     appyaml = self._ParseAppInfoFromYaml(self.basepath)
+    BackendsStatusUpdate(appyaml.runtime)
     rpcserver = self._GetRpcServer()
     response = rpcserver.Send('/api/backends/list', app_id=appyaml.application)
     print >> self.out_fh, response
@@ -3338,6 +3454,7 @@
 
     backend = self.args[0]
     appyaml = self._ParseAppInfoFromYaml(self.basepath)
+    BackendsStatusUpdate(appyaml.runtime)
     self.BackendsPhpCheck(appyaml)
     rpcserver = self._GetRpcServer()
     response = rpcserver.Send('/api/backends/start',
@@ -3352,6 +3469,7 @@
 
     backend = self.args[0]
     appyaml = self._ParseAppInfoFromYaml(self.basepath)
+    BackendsStatusUpdate(appyaml.runtime)
     rpcserver = self._GetRpcServer()
     response = rpcserver.Send('/api/backends/stop',
                               app_id=appyaml.application,
@@ -3365,6 +3483,7 @@
 
     backend = self.args[0]
     appyaml = self._ParseAppInfoFromYaml(self.basepath)
+    BackendsStatusUpdate(appyaml.runtime)
     rpcserver = self._GetRpcServer()
     response = rpcserver.Send('/api/backends/delete',
                               app_id=appyaml.application,
@@ -3378,6 +3497,7 @@
 
     backend = self.args[0]
     appyaml = self._ParseAppInfoFromYaml(self.basepath)
+    BackendsStatusUpdate(appyaml.runtime)
     self.BackendsPhpCheck(appyaml)
     backends_yaml = self._ParseBackendsYaml(self.basepath)
     rpcserver = self._GetRpcServer()
@@ -3565,7 +3685,16 @@
     """Sets the default version."""
     module = ''
     if len(self.args) == 1:
-      appyaml = self._ParseAppInfoFromYaml(self.args[0])
+
+
+
+      stored_modules = self.options.module
+      self.options.module = None
+      try:
+        appyaml = self._ParseAppInfoFromYaml(self.args[0])
+      finally:
+        self.options.module = stored_modules
+
       app_id = appyaml.application
       module = appyaml.module or ''
       version = appyaml.version
@@ -4344,8 +4473,11 @@
           short_desc='Set the default (serving) version.',
           long_desc="""
 The 'set_default_version' command sets the default (serving) version of the app.
- Defaults to using the application and version specified in app.yaml; use the
- --application and --version flags to override these values.""",
+Defaults to using the application, version and module specified in app.yaml;
+use the --application, --version and --module flags to override these values.
+The --module flag can also be a comma-delimited string of several modules. (ex.
+module1,module2,module2) In this case, the default version of each module will
+be changed to the version specified.""",
           uses_basepath=False),
 
       'resource_limits_info': Action(
@@ -4387,6 +4519,248 @@
   return True
 
 
+class JavaAppUpdate(object):
+  """Performs Java-specific update configurations."""
+  _JSP_REGEX = re.compile('.*\\.jspx?')
+
+
+
+
+  def __init__(self, basepath, options):
+    self.basepath = basepath
+    self.options = options
+
+    self.app_engine_web_xml = self._ReadAppEngineWebXml()
+    self.app_engine_web_xml.app_root = self.basepath
+    self.web_xml = self._ReadWebXml()
+
+  def _ReadAppEngineWebXml(self, basepath=None):
+    if not basepath:
+      basepath = self.basepath
+    return self._ReadAndParseXml(
+        basepath=basepath,
+        file_name='appengine-web.xml',
+        parser=app_engine_web_xml_parser.AppEngineWebXmlParser)
+
+  def _ReadWebXml(self, basepath=None):
+    if not basepath:
+      basepath = self.basepath
+    return self._ReadAndParseXml(
+        basepath=basepath,
+        file_name='web.xml',
+        parser=web_xml_parser.WebXmlParser)
+
+  def _ReadBackendsXml(self, basepath=None):
+    if not basepath:
+      basepath = self.basepath
+    return self._ReadAndParseXml(
+        basepath=basepath,
+        file_name='backends.xml',
+        parser=backends_xml_parser.BackendsXmlParser)
+
+  def _ReadAndParseXml(self, basepath, file_name, parser):
+    with open(os.path.join(basepath, 'WEB-INF', file_name)) as file_handle:
+      return parser().ProcessXml(file_handle.read())
+
+  def CreateStagingDirectory(self, sdk_root):
+    """Creates a staging directory for uploading.
+
+    This is where we perform the necessary actions to create an application
+    directory  for the update command to work properly - files are organized
+    into the static folder, and yaml files are generated where they can be
+    found later.
+
+    Args:
+      sdk_root: Path of the GAE SDK.
+
+    Returns:
+      The path to a new temporary directory which contains generated yaml files
+      and a static file directory. For the most part, the rest of the update and
+      upload flow can resume identically to Python/PHP/Go applications.
+    """
+    full_basepath = os.path.abspath(self.basepath)
+    stage_dir = tempfile.mkdtemp(prefix='appcfgpy')
+    static_dir = os.path.join(stage_dir, '__static__')
+    os.mkdir(static_dir)
+    self._CopyOrLink(full_basepath, stage_dir, static_dir, False)
+    self.app_engine_web_xml.app_root = stage_dir
+
+    if self.options.compile_jsps:
+      self._PrepareForJspCompilation(sdk_root, stage_dir)
+
+    self._GenerateAppYaml(stage_dir)
+
+    return stage_dir
+
+  def _GenerateAppYaml(self, stage_dir):
+    """Creates the app.yaml file in WEB-INF/appengine-generated/."""
+    backends = []
+    if os.path.isfile(os.path.join(self.basepath, 'WEB-INF', 'backends.xml')):
+      backends = self._ReadBackendsXml(stage_dir)
+    yaml_str = yaml_translator.AppYamlTranslator(
+        self.app_engine_web_xml,
+        backends,
+        self.web_xml,
+        self._GetStaticFileList(stage_dir),
+        None).GetYaml()
+    appengine_generated = os.path.join(
+        stage_dir, 'WEB-INF', 'appengine-generated')
+    if not os.path.isdir(appengine_generated):
+      os.mkdir(appengine_generated)
+    with open(os.path.join(appengine_generated, 'app.yaml'), 'w') as handle:
+      handle.write(yaml_str)
+
+  def _CopyOrLink(self, source_dir, stage_dir, static_dir, inside_web_inf):
+    for file_name in os.listdir(source_dir):
+      file_path = os.path.join(source_dir, file_name)
+
+      if file_name.startswith('.') or file_name == 'appengine-generated':
+        continue
+
+      if os.path.isdir(file_path):
+        self._CopyOrLink(
+            file_path,
+            os.path.join(stage_dir, file_name),
+            os.path.join(static_dir, file_name),
+            inside_web_inf or file_name == 'WEB-INF')
+      else:
+        if (inside_web_inf
+            or self.app_engine_web_xml.IncludesResource(file_path)
+            or (self.options.compile_jsps
+                and file_path.lower().endswith('.jsp'))):
+          self._CopyOrLinkFile(file_path, os.path.join(stage_dir, file_name))
+        if (not inside_web_inf
+            and self.app_engine_web_xml.IncludesStatic(file_path)):
+          self._CopyOrLinkFile(file_path, os.path.join(static_dir, file_name))
+
+  def _CopyOrLinkFile(self, source, dest):
+
+    if not os.path.exists(os.path.dirname(dest)):
+      os.makedirs(os.path.dirname(dest))
+    if not source.endswith('web.xml'):
+      os.symlink(source, dest)
+      return
+    shutil.copy(source, dest)
+
+  @staticmethod
+  def _GetStaticFileList(staging_dir):
+    static_files = []
+    for path, _, files in os.walk(os.path.join(staging_dir, '__static__')):
+      static_files += [os.path.join(path, f) for f in files]
+    return static_files
+
+  def _PrepareForJspCompilation(self, sdk_root, staging_dir):
+    """Performs necessary preparations for JSP Compilation."""
+    if self._MatchingFileExists(self._JSP_REGEX, staging_dir):
+      lib_dir = os.path.join(staging_dir, 'WEB-INF', 'lib')
+
+      for jar_file in GetUserJspLibFiles(sdk_root):
+        self._CopyOrLinkFile(
+            jar_file, os.path.join(lib_dir, os.path.basename(jar_file)))
+      for jar_file in GetSharedJspLibFiles(sdk_root):
+        self._CopyOrLinkFile(
+            jar_file, os.path.join(lib_dir, os.path.basename(jar_file)))
+
+      classes_dir = os.path.join(staging_dir, 'WEB-INF', 'classes')
+      gen_dir = tempfile.mkdtemp()
+
+      classpath = self._GetJspClasspath(sdk_root, classes_dir, gen_dir)
+
+      return classpath
+
+
+
+  @staticmethod
+  def _GetJspClasspath(sdk_root, classes_dir, gen_dir):
+    """Builds the classpath for the JSP Compilation system call."""
+    elements = GetImplLibs(sdk_root) + GetSharedLibFiles(sdk_root)
+    elements.append(classes_dir)
+    elements.append(gen_dir)
+
+    for path, _, files in os.walk(
+        os.path.join(os.path.dirname(classes_dir), 'lib')):
+      elements += [os.path.join(path, f) for f in files if
+                   f.endswith('.jar') or f.endswith('.zip')]
+
+    return (os.pathsep).join(elements)
+
+  @staticmethod
+  def _MatchingFileExists(regex, dir_path):
+    for _, _, files in os.walk(dir_path):
+      for f in files:
+        if re.search(regex, f):
+          return True
+    return False
+
+
+def GetImplLibs(sdk_root):
+  return _GetLibsShallow(os.path.join(sdk_root, 'java', 'lib', 'impl'))
+
+
+def GetSharedLibFiles(sdk_root):
+  return _GetLibsRecursive(os.path.join(sdk_root, 'java', 'lib', 'shared'))
+
+
+def GetUserJspLibFiles(sdk_root):
+  return _GetLibsRecursive(
+      os.path.join(sdk_root, 'java', 'lib', 'tools', 'jsp'))
+
+
+def GetSharedJspLibFiles(sdk_root):
+  return _GetLibsRecursive(
+      os.path.join(sdk_root, 'java', 'lib', 'shared', 'jsp'))
+
+
+def _GetLibsRecursive(dir_path):
+  libs = []
+  for path, _, files in os.walk(dir_path):
+    libs += [os.path.join(path, f) for f in files if f.endswith('.jar')]
+  return libs
+
+
+def _GetLibsShallow(dir_path):
+  libs = []
+  for f in os.listdir(dir_path):
+    if os.path.isfile(os.path.join(dir_path, f)) and f.endswith('.jar'):
+      libs.append(os.path.join(dir_path, f))
+  return libs
+
+
+def _JavaHome():
+  """Return the directory that the JDK is installed in.
+
+  The JDK install directory is expected to have a bin directory that contains
+  at a minimum the java and javac executables. If the environment variable
+  JAVA_HOME is set then it must point to such a directory. Otherwise, we look
+  for javac on the PATH and check that it is inside a JDK install directory.
+
+  Returns:
+    The JDK install directory.
+
+  Raises:
+    RuntimeError: If JAVA_HOME is set but is not a JDK install directory, or
+    otherwise if a JDK install directory cannot be found based on the PATH.
+  """
+  def IsValidJdk(path):
+    for binary in ['java', 'javac']:
+      binary_path = os.path.join(path, 'bin', binary)
+      if not os.path.isfile(binary_path) or not os.access(binary_path, os.X_OK):
+        return False
+    return True
+
+  java_home = os.getenv('JAVA_HOME')
+  if java_home:
+    if not IsValidJdk(java_home):
+      raise RuntimeError(
+          'JAVA_HOME is set but does not reference a valid JDK: %s' % java_home)
+    return java_home
+  for path_dir in os.environ['PATH'].split(os.pathsep):
+    maybe_root, last = os.path.split(path_dir)
+    if last == 'bin' and IsValidJdk(maybe_root):
+      return maybe_root
+  raise RuntimeError('Did not find JDK in PATH and JAVA_HOME is not set')
+
+
 def main(argv):
   logging.basicConfig(format=('%(asctime)s %(levelname)s %(filename)s:'
                               '%(lineno)s %(message)s '))
diff --git a/google/appengine/tools/backends_xml_parser.py b/google/appengine/tools/backends_xml_parser.py
new file mode 100644
index 0000000..8b67aca
--- /dev/null
+++ b/google/appengine/tools/backends_xml_parser.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Directly processes text of backends.xml.
+
+BackendsXmlParser is called with an XML string to produce a BackendsXml object
+containing the data from the XML.
+
+BackendsXmlParser: converts XML to BackendsXml objct
+Backend: describes a single backend specified in backends.xml
+
+"""
+
+from xml.etree import ElementTree
+
+from google.appengine.tools import xml_parser_utils
+from google.appengine.tools.app_engine_config_exception import AppEngineConfigException
+from google.appengine.tools.basic_equality_mixin import BasicEqualityMixin
+
+
+class BackendsXmlParser(object):
+  """Provides logic for walking down XML tree and pulling data."""
+
+  def ProcessXml(self, xml_str):
+    """Parses XML string and returns object representation of relevant info.
+
+    Args:
+      xml_str: The XML string.
+    Returns:
+      A list of Backend object containg information about backends from the XML.
+    Raises:
+      AppEngineConfigException: In case of malformed XML or illegal inputs.
+    """
+    try:
+      self.backends = []
+      self.errors = []
+      xml_root = ElementTree.fromstring(xml_str)
+
+      for child in xml_root.getchildren():
+        self.ProcessBackendNode(child)
+
+      if self.errors:
+        raise AppEngineConfigException('\n'.join(self.errors))
+
+      return self.backends
+    except ElementTree.ParseError:
+      raise AppEngineConfigException('Bad input -- not valid XML')
+
+  def ProcessBackendNode(self, node):
+    """Processes XML nodes labeled 'backend' into a BackendsXml object."""
+    tag = xml_parser_utils.GetTag(node)
+    if tag != 'backend':
+      self.errors.append('Unrecognized node: <%s>' % tag)
+      return
+
+    backend = Backend()
+    name = xml_parser_utils.GetAttribute(node, 'name')
+    if not name:
+      self.errors.append('All backends must have names')
+      backend.name = '-'
+    else:
+      backend.name = name
+    instance_class = xml_parser_utils.GetChildNodeText(node, 'class')
+    if instance_class:
+      backend.instance_class = instance_class
+    instances = xml_parser_utils.GetChildNodeText(node, 'instances')
+    if instances:
+      try:
+        backend.instances = int(instances)
+      except ValueError:
+        self.errors.append(
+            '<instances> must be an integer (bad value %s) in backend %s' %
+            (instances, backend.name))
+    max_concurrent_requests = xml_parser_utils.GetChildNodeText(
+        node, 'max-concurrent-requests')
+    if max_concurrent_requests:
+      try:
+        backend.max_concurrent_requests = int(max_concurrent_requests)
+      except ValueError:
+        self.errors.append('<max-concurrent-requests> must be an integer '
+                           '(bad value %s) in backend %s' %
+                           (max_concurrent_requests, backend.name))
+
+    options_node = xml_parser_utils.GetChild(node, 'options')
+    if options_node is not None:
+      for sub_node in options_node.getchildren():
+        tag = xml_parser_utils.GetTag(sub_node)
+        if tag not in ('fail-fast', 'dynamic', 'public'):
+          self.errors.append('<options> only supports values fail-fast, '
+                             'dynamic, and public (bad value %s) in backend %s'
+                             % (tag, backend.name))
+          continue
+        tag = tag.replace('-', '')
+        if xml_parser_utils.BooleanValue(sub_node.text):
+          backend.options.add(tag)
+        else:
+          if tag in backend.options:
+            backend.options.remove(tag)
+
+    self.backends.append(backend)
+
+
+class Backend(BasicEqualityMixin):
+  """Instances contain information about individual backend specifications."""
+
+  def __init__(self):
+    self.name = None
+    self.instances = None
+    self.instance_class = None
+    self.max_concurrent_requests = None
+    self.options = set()
+
+
diff --git a/google/appengine/tools/basic_equality_mixin.py b/google/appengine/tools/basic_equality_mixin.py
new file mode 100644
index 0000000..4bda24b
--- /dev/null
+++ b/google/appengine/tools/basic_equality_mixin.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""provides BasicEqualityMixin.
+
+BasicEqualityMixin provides equality methods which test based on
+equality of fields. This facilitates testing of translation modules.
+"""
+
+
+class BasicEqualityMixin(object):
+
+  def __eq__(self, other):
+    return (isinstance(other, self.__class__)
+            and self.__dict__ == other.__dict__)
+
+  def __ne__(self, other):
+    return not self.__eq__(other)
diff --git a/google/appengine/tools/boolean_action.py b/google/appengine/tools/boolean_action.py
index 4fe9a86..77a5527 100644
--- a/google/appengine/tools/boolean_action.py
+++ b/google/appengine/tools/boolean_action.py
@@ -26,14 +26,23 @@
 These syntaxes result a True value being assigned for the argument:
 --boolean_flag=yes    # "yes" is not case sensitive.
 --boolean_flag=true   # "true" is not case sensitive.
+--boolean_flag=1
 
 These syntaxes result a False value being assigned for the argument:
 --boolean_flag=no     # "no" is not case sensitive.
 --boolean_flag=false  # "false" is not case sensitive.
+--boolean_flag=0
+
+This syntax results in the value of the const parameter specified in the
+call to add_argument being assigned for the argument:
+--boolean_flag
 """
 
 import argparse
 
+_TRUE_VALUES = ['true', 'yes', '1']
+_FALSE_VALUES = ['false', 'no', '0']
+
 
 class BooleanAction(argparse.Action):
 
@@ -55,17 +64,21 @@
         help=help)
 
   def __call__(self, parser, namespace, values, option_string=None):
-    if isinstance(values, bool):
-      value = values
-    elif values:
-      value = values.lower()
-      if value in ['true', 'yes']:
-        value = True
-      elif value in ['false', 'no']:
-        value = False
-      else:
-        raise ValueError('must be "yes" or "no", not %r' % values)
-    else:
-      value = True
+    setattr(namespace, self.dest, BooleanParse(values))
 
-    setattr(namespace, self.dest, value)
+
+def BooleanParse(values):
+  if isinstance(values, bool):
+    return values
+  if values:
+    value = values.lower()
+    if value in _TRUE_VALUES:
+      return True
+    if value in _FALSE_VALUES:
+      return False
+    repr_values = (repr(value) for value in _TRUE_VALUES + _FALSE_VALUES)
+
+    raise ValueError('%r unrecognized boolean; known booleans are %s.' %
+                     (values, ', '.join(repr_values)))
+
+  return True
diff --git a/google/appengine/tools/bulkloader.py b/google/appengine/tools/bulkloader.py
index ce67163..5920e41 100644
--- a/google/appengine/tools/bulkloader.py
+++ b/google/appengine/tools/bulkloader.py
@@ -17,17 +17,6 @@
 
 
 
-
-
-
-
-
-
-
-
-
-
-
 """Imports data over HTTP.
 
 Usage:
diff --git a/google/appengine/tools/dev-channel-js.js b/google/appengine/tools/dev-channel-js.js
index 6eaac9f..6b0bacb 100644
--- a/google/appengine/tools/dev-channel-js.js
+++ b/google/appengine/tools/dev-channel-js.js
@@ -647,7 +647,7 @@
 };
 goog.string.removeAt = function(s, index, stringLength) {
   var resultStr = s;
-  0 <= index && (index < s.length && 0 < stringLength) && (resultStr = s.substr(0, index) + s.substr(index + stringLength, s.length - index - stringLength));
+  0 <= index && index < s.length && 0 < stringLength && (resultStr = s.substr(0, index) + s.substr(index + stringLength, s.length - index - stringLength));
   return resultStr
 };
 goog.string.remove = function(s, ss) {
@@ -1224,6 +1224,11 @@
     return f.apply(this, Array.prototype.slice.call(arguments, 0, opt_numArgs))
   }
 };
+goog.functions.nth = function(n) {
+  return function() {
+    return arguments[n]
+  }
+};
 goog.functions.withReturnValue = function(f, retValue) {
   return goog.functions.sequence(f, goog.functions.constant(retValue))
 };
@@ -2370,16 +2375,30 @@
 goog.dom.TAGS_TO_IGNORE_ = {SCRIPT:1, STYLE:1, HEAD:1, IFRAME:1, OBJECT:1};
 goog.dom.PREDEFINED_TAG_VALUES_ = {IMG:" ", BR:"\n"};
 goog.dom.isFocusableTabIndex = function(element) {
-  var attrNode = element.getAttributeNode("tabindex");
-  if(attrNode && attrNode.specified) {
-    var index = element.tabIndex;
-    return goog.isNumber(index) && 0 <= index && 32768 > index
-  }
-  return!1
+  return goog.dom.hasSpecifiedTabIndex_(element) && goog.dom.isTabIndexFocusable_(element)
 };
 goog.dom.setFocusableTabIndex = function(element, enable) {
   enable ? element.tabIndex = 0 : (element.tabIndex = -1, element.removeAttribute("tabIndex"))
 };
+goog.dom.isFocusable = function(element) {
+  var focusable;
+  return(focusable = goog.dom.isFormElement_(element) ? !element.disabled && (!goog.dom.hasSpecifiedTabIndex_(element) || goog.dom.isTabIndexFocusable_(element)) : goog.dom.isFocusableTabIndex(element)) && goog.userAgent.IE ? goog.dom.hasNonZeroBoundingRect_(element) : focusable
+};
+goog.dom.hasSpecifiedTabIndex_ = function(element) {
+  var attrNode = element.getAttributeNode("tabindex");
+  return goog.isDefAndNotNull(attrNode) && attrNode.specified
+};
+goog.dom.isTabIndexFocusable_ = function(element) {
+  var index = element.tabIndex;
+  return goog.isNumber(index) && 0 <= index && 32768 > index
+};
+goog.dom.isFormElement_ = function(element) {
+  return element.tagName == goog.dom.TagName.INPUT || element.tagName == goog.dom.TagName.TEXTAREA || element.tagName == goog.dom.TagName.SELECT || element.tagName == goog.dom.TagName.BUTTON
+};
+goog.dom.hasNonZeroBoundingRect_ = function(element) {
+  var rect = goog.isFunction(element.getBoundingClientRect) ? element.getBoundingClientRect() : {height:element.offsetHeight, width:element.offsetWidth};
+  return goog.isDefAndNotNull(rect) && 0 < rect.height && 0 < rect.width
+};
 goog.dom.getTextContent = function(node) {
   var textContent;
   if(goog.dom.BrowserFeature.CAN_USE_INNER_TEXT && "innerText" in node) {
@@ -2595,6 +2614,7 @@
 goog.dom.DomHelper.prototype.findNodes = goog.dom.findNodes;
 goog.dom.DomHelper.prototype.isFocusableTabIndex = goog.dom.isFocusableTabIndex;
 goog.dom.DomHelper.prototype.setFocusableTabIndex = goog.dom.setFocusableTabIndex;
+goog.dom.DomHelper.prototype.isFocusable = goog.dom.isFocusable;
 goog.dom.DomHelper.prototype.getTextContent = goog.dom.getTextContent;
 goog.dom.DomHelper.prototype.getNodeTextLength = goog.dom.getNodeTextLength;
 goog.dom.DomHelper.prototype.getNodeTextOffset = goog.dom.getNodeTextOffset;
@@ -2735,7 +2755,7 @@
 DRAGSTART:"dragstart", DRAG:"drag", DRAGENTER:"dragenter", DRAGOVER:"dragover", DRAGLEAVE:"dragleave", DROP:"drop", DRAGEND:"dragend", TOUCHSTART:"touchstart", TOUCHMOVE:"touchmove", TOUCHEND:"touchend", TOUCHCANCEL:"touchcancel", BEFOREUNLOAD:"beforeunload", CONSOLEMESSAGE:"consolemessage", CONTEXTMENU:"contextmenu", DOMCONTENTLOADED:"DOMContentLoaded", ERROR:"error", HELP:"help", LOAD:"load", LOSECAPTURE:"losecapture", ORIENTATIONCHANGE:"orientationchange", READYSTATECHANGE:"readystatechange", 
 RESIZE:"resize", SCROLL:"scroll", UNLOAD:"unload", HASHCHANGE:"hashchange", PAGEHIDE:"pagehide", PAGESHOW:"pageshow", POPSTATE:"popstate", COPY:"copy", PASTE:"paste", CUT:"cut", BEFORECOPY:"beforecopy", BEFORECUT:"beforecut", BEFOREPASTE:"beforepaste", ONLINE:"online", OFFLINE:"offline", MESSAGE:"message", CONNECT:"connect", ANIMATIONSTART:goog.events.getVendorPrefixedName_("AnimationStart"), ANIMATIONEND:goog.events.getVendorPrefixedName_("AnimationEnd"), ANIMATIONITERATION:goog.events.getVendorPrefixedName_("AnimationIteration"), 
 TRANSITIONEND:goog.events.getVendorPrefixedName_("TransitionEnd"), MSGESTURECHANGE:"MSGestureChange", MSGESTUREEND:"MSGestureEnd", MSGESTUREHOLD:"MSGestureHold", MSGESTURESTART:"MSGestureStart", MSGESTURETAP:"MSGestureTap", MSGOTPOINTERCAPTURE:"MSGotPointerCapture", MSINERTIASTART:"MSInertiaStart", MSLOSTPOINTERCAPTURE:"MSLostPointerCapture", MSPOINTERCANCEL:"MSPointerCancel", MSPOINTERDOWN:"MSPointerDown", MSPOINTERMOVE:"MSPointerMove", MSPOINTEROVER:"MSPointerOver", MSPOINTEROUT:"MSPointerOut", 
-MSPOINTERUP:"MSPointerUp", TEXTINPUT:"textinput", COMPOSITIONSTART:"compositionstart", COMPOSITIONUPDATE:"compositionupdate", COMPOSITIONEND:"compositionend", EXIT:"exit", LOADABORT:"loadabort", LOADCOMMIT:"loadcommit", LOADREDIRECT:"loadredirect", LOADSTART:"loadstart", LOADSTOP:"loadstop", RESPONSIVE:"responsive", SIZECHANGED:"sizechanged", UNRESPONSIVE:"unresponsive"};
+MSPOINTERUP:"MSPointerUp", TEXTINPUT:"textinput", COMPOSITIONSTART:"compositionstart", COMPOSITIONUPDATE:"compositionupdate", COMPOSITIONEND:"compositionend", EXIT:"exit", LOADABORT:"loadabort", LOADCOMMIT:"loadcommit", LOADREDIRECT:"loadredirect", LOADSTART:"loadstart", LOADSTOP:"loadstop", RESPONSIVE:"responsive", SIZECHANGED:"sizechanged", UNRESPONSIVE:"unresponsive", VISIBILITYCHANGE:"visibilitychange"};
 goog.events.BrowserEvent = function(opt_e, opt_currentTarget) {
   opt_e && this.init(opt_e, opt_currentTarget)
 };
@@ -2757,7 +2777,6 @@
 goog.events.BrowserEvent.prototype.altKey = !1;
 goog.events.BrowserEvent.prototype.shiftKey = !1;
 goog.events.BrowserEvent.prototype.metaKey = !1;
-goog.events.BrowserEvent.prototype.platformModifierKey = !1;
 goog.events.BrowserEvent.prototype.event_ = null;
 goog.events.BrowserEvent.prototype.init = function(e, opt_currentTarget) {
   var type = this.type = e.type;
@@ -2780,7 +2799,6 @@
   this.altKey = e.altKey;
   this.shiftKey = e.shiftKey;
   this.metaKey = e.metaKey;
-  this.platformModifierKey = goog.userAgent.MAC ? e.metaKey : e.ctrlKey;
   this.state = e.state;
   this.event_ = e;
   e.defaultPrevented && this.preventDefault();
@@ -2815,7 +2833,11 @@
   cls.prototype[goog.events.Listenable.IMPLEMENTED_BY_PROP] = !0
 };
 goog.events.Listenable.isImplementedBy = function(obj) {
-  return!(!obj || !obj[goog.events.Listenable.IMPLEMENTED_BY_PROP])
+  try {
+    return!(!obj || !obj[goog.events.Listenable.IMPLEMENTED_BY_PROP])
+  }catch(e) {
+    return!1
+  }
 };
 goog.events.ListenableKey = function() {
 };
@@ -2846,13 +2868,6 @@
 goog.events.ListenerMap.prototype.getTypeCount = function() {
   return this.typeCount_
 };
-goog.events.ListenerMap.prototype.getListenerCount = function() {
-  var count = 0, type;
-  for(type in this.listeners) {
-    count += this.listeners[type].length
-  }
-  return count
-};
 goog.events.ListenerMap.prototype.add = function(type, listener, callOnce, opt_useCapture, opt_listenerScope) {
   var listenerArray = this.listeners[type];
   listenerArray || (listenerArray = this.listeners[type] = [], this.typeCount_++);
@@ -2932,11 +2947,12 @@
   return-1
 };
 goog.events.listeners_ = {};
-goog.events.listenerTree_ = {};
+goog.events.LISTENER_MAP_PROP_ = "closure_lm_" + (1E6 * Math.random() | 0);
 goog.events.onString_ = "on";
 goog.events.onStringMap_ = {};
 goog.events.CaptureSimulationMode = {OFF_AND_FAIL:0, OFF_AND_SILENT:1, ON:2};
 goog.events.CAPTURE_SIMULATION_MODE = 2;
+goog.events.listenerCountEstimate_ = 0;
 goog.events.listen = function(src, type, listener, opt_capt, opt_handler) {
   if(goog.isArray(type)) {
     for(var i = 0;i < type.length;i++) {
@@ -2960,8 +2976,8 @@
       return null
     }
   }
-  var srcUid = goog.getUid(src), listenerMap = goog.events.listenerTree_[srcUid];
-  listenerMap || (goog.events.listenerTree_[srcUid] = listenerMap = new goog.events.ListenerMap(src));
+  var listenerMap = goog.events.getListenerMap_(src);
+  listenerMap || (src[goog.events.LISTENER_MAP_PROP_] = listenerMap = new goog.events.ListenerMap(src));
   var listenerObj = listenerMap.add(type, listener, callOnce, opt_capt, opt_handler);
   if(listenerObj.proxy) {
     return listenerObj
@@ -2971,7 +2987,8 @@
   proxy.src = src;
   proxy.listener = listenerObj;
   src.addEventListener ? src.addEventListener(type, proxy, capture) : src.attachEvent(goog.events.getOnString_(type), proxy);
-  return goog.events.listeners_[listenerObj.key] = listenerObj
+  goog.events.listenerCountEstimate_++;
+  return listenerObj
 };
 goog.events.getProxy = function() {
   var proxyCallbackFunction = goog.events.handleBrowserEvent_, f = goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT ? function(eventObject) {
@@ -3034,19 +3051,22 @@
   }
   var type = listener.type, proxy = listener.proxy;
   src.removeEventListener ? src.removeEventListener(type, proxy, listener.capture) : src.detachEvent && src.detachEvent(goog.events.getOnString_(type), proxy);
+  goog.events.listenerCountEstimate_--;
   var listenerMap = goog.events.getListenerMap_(src);
-  listenerMap ? (listenerMap.removeByKey(listener), 0 == listenerMap.getTypeCount() && (listenerMap.src = null, delete goog.events.listenerTree_[goog.getUid(src)])) : listener.markAsRemoved();
-  delete goog.events.listeners_[listener.key];
+  listenerMap ? (listenerMap.removeByKey(listener), 0 == listenerMap.getTypeCount() && (listenerMap.src = null, src[goog.events.LISTENER_MAP_PROP_] = null)) : listener.markAsRemoved();
   return!0
 };
 goog.events.unlistenWithWrapper = function(src, wrapper, listener, opt_capt, opt_handler) {
   wrapper.unlisten(src, listener, opt_capt, opt_handler)
 };
 goog.events.removeAll = function(opt_obj, opt_type) {
-  return opt_obj ? goog.events.Listenable.isImplementedBy(opt_obj) ? opt_obj.removeAllListeners(opt_type) : goog.events.removeAll_(goog.getUid(opt_obj), opt_type) : goog.events.removeAllNativeListeners()
-};
-goog.events.removeAll_ = function(srcUid, opt_type) {
-  var listenerMap = goog.events.listenerTree_[srcUid];
+  if(!opt_obj) {
+    return 0
+  }
+  if(goog.events.Listenable.isImplementedBy(opt_obj)) {
+    return opt_obj.removeAllListeners(opt_type)
+  }
+  var listenerMap = goog.events.getListenerMap_(opt_obj);
   if(!listenerMap) {
     return 0
   }
@@ -3061,11 +3081,7 @@
   return count
 };
 goog.events.removeAllNativeListeners = function() {
-  var count = 0, srcUid;
-  for(srcUid in goog.events.listenerTree_) {
-    count += goog.events.removeAll_(srcUid)
-  }
-  return count
+  return goog.events.listenerCountEstimate_ = 0
 };
 goog.events.getListeners = function(obj, type, capture) {
   if(goog.events.Listenable.isImplementedBy(obj)) {
@@ -3116,7 +3132,7 @@
     if(listenerArray) {
       for(var listenerArray = goog.array.clone(listenerArray), i = 0;i < listenerArray.length;i++) {
         var listener = listenerArray[i];
-        listener && (listener.capture == capture && !listener.removed) && (retval &= !1 !== goog.events.fireListener(listener, eventObject))
+        listener && listener.capture == capture && !listener.removed && (retval &= !1 !== goog.events.fireListener(listener, eventObject))
       }
     }
   }
@@ -3128,11 +3144,7 @@
   return listenerFn.call(listenerHandler, eventObject)
 };
 goog.events.getTotalListenerCount = function() {
-  var count = 0, srcUid;
-  for(srcUid in goog.events.listenerTree_) {
-    var listenerMap = goog.events.listenerTree_[srcUid], count = count + listenerMap.getListenerCount()
-  }
-  return count
+  return goog.events.listenerCountEstimate_
 };
 goog.events.dispatchEvent = function(src, e) {
   goog.asserts.assert(goog.events.Listenable.isImplementedBy(src), "Can not use goog.events.dispatchEvent with non-goog.events.Listenable instance.");
@@ -3189,7 +3201,8 @@
   return identifier + "_" + goog.events.uniqueIdCounter_++
 };
 goog.events.getListenerMap_ = function(src) {
-  return goog.hasUid(src) ? goog.events.listenerTree_[goog.getUid(src)] || null : null
+  var listenerMap = src[goog.events.LISTENER_MAP_PROP_];
+  return listenerMap instanceof goog.events.ListenerMap ? listenerMap : null
 };
 goog.events.LISTENER_WRAPPER_PROP_ = "__closure_events_fn_" + (1E9 * Math.random() >>> 0);
 goog.events.wrapListener_ = function(listener) {
@@ -3704,6 +3717,7 @@
 goog.structs.Map = function(opt_map, var_args) {
   this.map_ = {};
   this.keys_ = [];
+  this.version_ = this.count_ = 0;
   var argLength = arguments.length;
   if(1 < argLength) {
     if(argLength % 2) {
@@ -3716,8 +3730,6 @@
     opt_map && this.addAll(opt_map)
   }
 };
-goog.structs.Map.prototype.count_ = 0;
-goog.structs.Map.prototype.version_ = 0;
 goog.structs.Map.prototype.getCount = function() {
   return this.count_
 };
@@ -4244,13 +4256,12 @@
 goog.debug.LogRecord = function(level, msg, loggerName, opt_time, opt_sequenceNumber) {
   this.reset(level, msg, loggerName, opt_time, opt_sequenceNumber)
 };
-goog.debug.LogRecord.prototype.sequenceNumber_ = 0;
 goog.debug.LogRecord.prototype.exception_ = null;
 goog.debug.LogRecord.prototype.exceptionText_ = null;
 goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS = !0;
 goog.debug.LogRecord.nextSequenceNumber_ = 0;
 goog.debug.LogRecord.prototype.reset = function(level, msg, loggerName, opt_time, opt_sequenceNumber) {
-  goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS && (this.sequenceNumber_ = "number" == typeof opt_sequenceNumber ? opt_sequenceNumber : goog.debug.LogRecord.nextSequenceNumber_++);
+  goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS && ("number" == typeof opt_sequenceNumber || goog.debug.LogRecord.nextSequenceNumber_++);
   opt_time || goog.now();
   this.level_ = level;
   this.msg_ = msg;
@@ -4970,7 +4981,7 @@
     headers.set(key, value)
   });
   var contentTypeKey = goog.array.find(headers.getKeys(), goog.net.XhrIo.isContentTypeHeader_), contentIsFormData = goog.global.FormData && content instanceof goog.global.FormData;
-  !goog.array.contains(goog.net.XhrIo.METHODS_WITH_FORM_DATA, method) || (contentTypeKey || contentIsFormData) || headers.set(goog.net.XhrIo.CONTENT_TYPE_HEADER, goog.net.XhrIo.FORM_CONTENT_TYPE);
+  !goog.array.contains(goog.net.XhrIo.METHODS_WITH_FORM_DATA, method) || contentTypeKey || contentIsFormData || headers.set(goog.net.XhrIo.CONTENT_TYPE_HEADER, goog.net.XhrIo.FORM_CONTENT_TYPE);
   goog.structs.forEach(headers, function(value, key) {
     this.xhr_.setRequestHeader(key, value)
   }, this);
diff --git a/google/appengine/tools/dev_appserver.py b/google/appengine/tools/dev_appserver.py
index 930e63c..4352cd6 100644
--- a/google/appengine/tools/dev_appserver.py
+++ b/google/appengine/tools/dev_appserver.py
@@ -114,7 +114,6 @@
 from google.appengine.api.app_identity import app_identity_stub
 from google.appengine.api.blobstore import blobstore_stub
 from google.appengine.api.blobstore import file_blob_storage
-
 from google.appengine.api.capabilities import capability_stub
 from google.appengine.api.channel import channel_service_stub
 from google.appengine.api.files import file_service_stub
@@ -3658,10 +3657,6 @@
 
 
 
-
-
-
-
   try:
     from google.appengine.api.images import images_stub
     host_prefix = 'http://%s:%d' % (serve_address, serve_port)
diff --git a/google/appengine/tools/devappserver2/admin/templates/console.js b/google/appengine/tools/devappserver2/admin/templates/console.js
index f5ab081..32199f7 100644
--- a/google/appengine/tools/devappserver2/admin/templates/console.js
+++ b/google/appengine/tools/devappserver2/admin/templates/console.js
@@ -19,6 +19,17 @@
 /**
  * @private
  */
+var DEFAULT_PHP_SOURCE_ =
+    'require_once \'google/appengine/api/mail/Message.php\';\n' +
+    'use \\google\\appengine\\api\\mail\\Message;\n' +
+    'require_once \'google/appengine/api/users/UserService.php\';\n' +
+    'use google\\appengine\\api\\users\\UserService;\n' +
+    '\n' +
+    'var_dump($_SERVER);\n';
+
+/**
+ * @private
+ */
 var SERVER_NAME_TO_RUNTIME_NAME_ = {
 {% for module in modules %}
   '{{ module.name }}': '{{ module.module_configuration.runtime }}',
@@ -36,9 +47,11 @@
 function getCode(moduleName) {
   var text = localStorage.getItem('{{ app_id }}:' + moduleName);
   if (text == null) {
-    if (SERVER_NAME_TO_RUNTIME_NAME_[moduleName] == 'python' ||
-        SERVER_NAME_TO_RUNTIME_NAME_[moduleName] == 'python27') {
+    var runtime = SERVER_NAME_TO_RUNTIME_NAME_[moduleName];
+    if (runtime == 'python' || runtime  == 'python27') {
       return DEFAULT_PYTHON_SOURCE_;
+    } else if (runtime == 'php') {
+      return DEFAULT_PHP_SOURCE_;
     } else {
       return '';
     }
diff --git a/google/appengine/tools/devappserver2/admin/templates/skeleton.html b/google/appengine/tools/devappserver2/admin/templates/skeleton.html
index 4bf5bdf..e69e9f5 100644
--- a/google/appengine/tools/devappserver2/admin/templates/skeleton.html
+++ b/google/appengine/tools/devappserver2/admin/templates/skeleton.html
@@ -9,7 +9,6 @@
 <html>
 <head>
   <title>{% block page_title %}{% endblock %}</title>
-  <link href='http://fonts.googleapis.com/css?family=Open+Sans:400,600,300,700' rel='stylesheet' type='text/css'>
   <link rel="stylesheet" href="/assets/common.css" type="text/css" media="screen">
   <script src='/assets/jquery-1.8.2.min.js'></script>
   {% block page_head %}{% endblock %}
diff --git a/google/appengine/tools/devappserver2/api_server.py b/google/appengine/tools/devappserver2/api_server.py
index 74cec4d..ba48730 100644
--- a/google/appengine/tools/devappserver2/api_server.py
+++ b/google/appengine/tools/devappserver2/api_server.py
@@ -167,9 +167,6 @@
         application_error = response.mutable_application_error()
         application_error.set_code(e.application_error)
         application_error.set_detail(e.error_detail)
-        # TODO: is this necessary? Python remote stub ignores exception
-        # when application error is specified; do other runtimes use it?
-        response.set_exception(pickle.dumps(e))
       else:
         # If the runtime instance is not Python, it won't be able to unpickle
         # the exception so use level that won't be ignored by default.
@@ -177,7 +174,10 @@
         # Even if the runtime is Python, the exception may be unpicklable if
         # it requires importing a class blocked by the sandbox so just send
         # back the exception representation.
-        response.set_exception(pickle.dumps(RuntimeError(repr(e))))
+        e = RuntimeError(repr(e))
+      # While not strictly necessary for ApplicationError, do this to limit
+      # differences with remote_api:handler.py.
+      response.set_exception(pickle.dumps(e))
       logging.log(level, 'Exception while handling %s\n%s', request,
                   traceback.format_exc())
     encoded_response = response.Encode()
@@ -201,7 +201,7 @@
     elif environ['REQUEST_METHOD'] == 'POST':
       return self._handle_POST(environ, start_response)
     else:
-      start_response('405 Method Not Allowed')
+      start_response('405 Method Not Allowed', [])
       return []
 
 
diff --git a/google/appengine/tools/devappserver2/api_server_test.py b/google/appengine/tools/devappserver2/api_server_test.py
index fe2635b..d93b82d 100644
--- a/google/appengine/tools/devappserver2/api_server_test.py
+++ b/google/appengine/tools/devappserver2/api_server_test.py
@@ -163,6 +163,15 @@
                         self.server,
                         environ)
 
+  def test_unsupported_method(self):
+    environ = {'REQUEST_METHOD': 'HEAD',
+               'QUERY_STRING': 'rtok=23'}
+    self.assertResponse('405 Method Not Allowed',
+                        {},
+                        '',
+                        self.server,
+                        environ)
+
   def test_exception(self):
     urlfetch_request = urlfetch_service_pb.URLFetchRequest()
     urlfetch_request.set_url('exception')
@@ -192,4 +201,3 @@
 
 if __name__ == '__main__':
   unittest.main()
-
diff --git a/google/appengine/tools/devappserver2/devappserver2.py b/google/appengine/tools/devappserver2/devappserver2.py
index 332237b..b027f2d 100644
--- a/google/appengine/tools/devappserver2/devappserver2.py
+++ b/google/appengine/tools/devappserver2/devappserver2.py
@@ -121,13 +121,20 @@
 
 def _get_default_php_path():
   """Returns the path to the siloed php-cgi binary or None if not present."""
+  default_php_executable_path = None
   if sys.platform == 'win32':
     default_php_executable_path = os.path.abspath(
         os.path.join(os.path.dirname(sys.argv[0]),
-                     'php/php-5.4.15-Win32-VC9-x86/php-cgi.exe'))
-    if os.path.exists(default_php_executable_path):
-      return default_php_executable_path
+                     'php/php-5.4-Win32-VC9-x86/php-cgi.exe'))
+  elif sys.platform == 'darwin':
+    default_php_executable_path = os.path.abspath(
+        os.path.join(
+            os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0]))),
+            'php-cgi'))
 
+  if (default_php_executable_path and
+      os.path.exists(default_php_executable_path)):
+    return default_php_executable_path
   return None
 
 
@@ -147,6 +154,81 @@
     return port
 
 
+def parse_per_module_option(
+    value, value_type, value_predicate,
+    single_bad_type_error, single_bad_predicate_error,
+    multiple_bad_type_error, multiple_bad_predicate_error,
+    multiple_duplicate_module_error):
+  """Parses command line options that may be specified per-module.
+
+  Args:
+    value: A str containing the flag value to parse. Two formats are supported:
+        1. A universal value (may not contain a colon as that is use to
+           indicate a per-module value).
+        2. Per-module values. One or more comma separated module-value pairs.
+           Each pair is a module_name:value. An empty module-name is shorthand
+           for "default" to match how not specifying a module name in the yaml
+           is the same as specifying "module: default".
+    value_type: a callable that converts the string representation of the value
+        to the actual value. Should raise ValueError if the string can not
+        be converted.
+    value_predicate: a predicate to call on the converted value to validate
+        the converted value. Use "lambda _: True" if all values are valid.
+    single_bad_type_error: the message to use if a universal value is provided
+        and value_type throws a ValueError. The message must consume a single
+        format parameter (the provided value).
+    single_bad_predicate_error: the message to use if a universal value is
+        provided and value_predicate returns False. The message does not
+        get any format parameters.
+    multiple_bad_type_error: the message to use if a per-module value
+        either does not have two values separated by a single colon or if
+        value_types throws a ValueError on the second string. The message must
+        consume a single format parameter (the module_name:value pair).
+    multiple_bad_predicate_error: the message to use if a per-module value if
+        value_predicate returns False. The message must consume a single format
+        parameter (the module name).
+    multiple_duplicate_module_error: the message to use if the same module is
+        repeated. The message must consume a single formater parameter (the
+        module name).
+
+  Returns:
+    Either a single value of value_type for universal values or a dict of
+    str->value_type for per-module values.
+
+  Raises:
+    argparse.ArgumentTypeError: the value is invalid.
+  """
+  if ':' not in value:
+    try:
+      single_value = value_type(value)
+    except ValueError:
+      raise argparse.ArgumentTypeError(single_bad_type_error % value)
+    else:
+      if not value_predicate(single_value):
+        raise argparse.ArgumentTypeError(single_bad_predicate_error)
+      return single_value
+  else:
+    module_to_value = {}
+    for module_value in value.split(','):
+      try:
+        module_name, single_value = module_value.split(':')
+        single_value = value_type(single_value)
+      except ValueError:
+        raise argparse.ArgumentTypeError(multiple_bad_type_error % module_value)
+      else:
+        module_name = module_name.strip()
+        if not module_name:
+          module_name = 'default'
+        if module_name in module_to_value:
+          raise argparse.ArgumentTypeError(
+              multiple_duplicate_module_error % module_name)
+        if not value_predicate(single_value):
+          raise argparse.ArgumentTypeError(
+              multiple_bad_predicate_error % module_name)
+        module_to_value[module_name] = single_value
+    return module_to_value
+
+
 def parse_max_module_instances(value):
   """Returns the parsed value for the --max_module_instances flag.
 
@@ -167,37 +249,46 @@
   Raises:
     argparse.ArgumentTypeError: the value is invalid.
   """
-  if ':' not in value:
-    try:
-      max_module_instances = int(value)
-    except ValueError:
-      raise argparse.ArgumentTypeError('Invalid instance count: %r' % value)
-    else:
-      if not max_module_instances:
-        raise argparse.ArgumentTypeError(
-            'Cannot specify zero instances for all modules')
-      return max_module_instances
-  else:
-    module_to_max_instances = {}
-    for module_instance_max in value.split(','):
-      try:
-        module_name, max_instances = module_instance_max.split(':')
-        max_instances = int(max_instances)
-      except ValueError:
-        raise argparse.ArgumentTypeError(
-            'Expected "module:max_instances": %r' % module_instance_max)
-      else:
-        module_name = module_name.strip()
-        if not module_name:
-          module_name = 'default'
-        if module_name in module_to_max_instances:
-          raise argparse.ArgumentTypeError(
-              'Duplicate max instance value: %r' % module_name)
-        if not max_instances:
-          raise argparse.ArgumentTypeError(
-              'Cannot specify zero instances for module %s' % module_name)
-        module_to_max_instances[module_name] = max_instances
-    return module_to_max_instances
+  # TODO: disallow negative values.
+  return parse_per_module_option(
+      value, int, lambda x: x,
+      'Invalid instance count: %r',
+      'Cannot specify zero instances for all modules',
+      'Expected "module:max_instances": %r',
+      'Cannot specify zero instances for module %s',
+      'Duplicate max instance value for module %s')
+
+
+def parse_threadsafe_override(value):
+  """Returns the parsed value for the --threadsafe_override flag.
+
+  Args:
+    value: A str containing the flag value for parse. The format should follow
+        one of the following examples:
+          1. "False" - All modules override the YAML threadsafe configuration
+             as if the YAML contained False.
+          2. "default:False,backend:True" - The default module overrides the
+             YAML threadsafe configuration as if the YAML contained False, the
+             "backend" module overrides with a value of True and all other
+             modules use the value in the YAML file. An empty name (i.e.
+             ":True") is shorthand for default to match how not specifying a
+             module name in the yaml is the same as specifying
+             "module: default".
+  Returns:
+    The parsed value of the threadsafe_override flag. May either be a bool
+    (for values of the form "False") or a dict of str->bool (for values of the
+    form "default:False,backend:True").
+
+  Raises:
+    argparse.ArgumentTypeError: the value is invalid.
+  """
+  return parse_per_module_option(
+      value, boolean_action.BooleanParse, lambda _: True,
+      'Invalid threadsafe override: %r',
+      None,
+      'Expected "module:threadsafe_override": %r',
+      None,
+      'Duplicate threadsafe override value for module %s')
 
 
 def parse_path(value):
@@ -254,6 +345,13 @@
       default=False,
       help='use mtime polling for detecting source code changes - useful if '
       'modifying code from a remote machine using a distributed file system')
+  common_group.add_argument(
+      '--threadsafe_override',
+      type=parse_threadsafe_override,
+      help='override the application\'s threadsafe configuration - the value '
+      'can be a boolean, in which case all modules threadsafe setting will '
+      'be overridden or a comma-separated list of module:threadsafe_override '
+      'e.g. "default:False,backend:True"')
 
   # PHP
   php_group = parser.add_argument_group('PHP')
@@ -439,6 +537,11 @@
       default=False,
       help='make files specified in the app.yaml "skip_files" or "static" '
       'handles readable by the application.')
+  # No help to avoid lengthening help message for rarely used feature:
+  # host name to which the server for API calls should bind.
+  misc_group.add_argument(
+      '--api_host', default='localhost',
+      help=argparse.SUPPRESS)
   misc_group.add_argument(
       '--api_port', type=PortParser(), default=0,
       help='port to which the server for API calls should bind')
@@ -564,23 +667,6 @@
 
     _setup_environ(configuration.app_id)
 
-    python_config = runtime_config_pb2.PythonConfig()
-    if options.python_startup_script:
-      python_config.startup_script = os.path.abspath(
-          options.python_startup_script)
-      if options.python_startup_args:
-        python_config.startup_args = options.python_startup_args
-
-    php_executable_path = (options.php_executable_path and
-                           os.path.abspath(options.php_executable_path))
-    cloud_sql_config = runtime_config_pb2.CloudSQL()
-    cloud_sql_config.mysql_host = options.mysql_host
-    cloud_sql_config.mysql_port = options.mysql_port
-    cloud_sql_config.mysql_user = options.mysql_user
-    cloud_sql_config.mysql_password = options.mysql_password
-    if options.mysql_socket:
-      cloud_sql_config.mysql_socket = options.mysql_socket
-
     if options.max_module_instances is None:
       module_to_max_instances = {}
     elif isinstance(options.max_module_instances, int):
@@ -590,28 +676,57 @@
     else:
       module_to_max_instances = options.max_module_instances
 
+    if options.threadsafe_override is None:
+      module_to_threadsafe_override = {}
+    elif isinstance(options.threadsafe_override, bool):
+      module_to_threadsafe_override = {
+          module_configuration.module_name: options.threadsafe_override
+          for module_configuration in configuration.modules}
+    else:
+      module_to_threadsafe_override = options.threadsafe_override
+
     self._dispatcher = dispatcher.Dispatcher(
         configuration,
         options.host,
         options.port,
         options.auth_domain,
         _LOG_LEVEL_TO_RUNTIME_CONSTANT[options.log_level],
-        php_executable_path,
-        options.php_remote_debugging,
-        python_config,
-        cloud_sql_config,
+        self._create_php_config(options),
+        self._create_python_config(options),
+        self._create_cloud_sql_config(options),
         module_to_max_instances,
         options.use_mtime_file_watcher,
         options.automatic_restart,
-        options.allow_skipped_files)
+        options.allow_skipped_files,
+        module_to_threadsafe_override)
 
     request_data = wsgi_request_info.WSGIRequestInfo(self._dispatcher)
-
     storage_path = _get_storage_path(options.storage_path, configuration.app_id)
+
+    apis = self._create_api_server(
+        request_data, storage_path, options, configuration)
+    apis.start()
+    self._running_modules.append(apis)
+
+    self._dispatcher.start(options.api_host, apis.port, request_data)
+    self._running_modules.append(self._dispatcher)
+
+    xsrf_path = os.path.join(storage_path, 'xsrf')
+    admin = admin_server.AdminServer(options.admin_host, options.admin_port,
+                                     self._dispatcher, configuration, xsrf_path)
+    admin.start()
+    self._running_modules.append(admin)
+
+  def stop(self):
+    """Stops all running devappserver2 modules."""
+    while self._running_modules:
+      self._running_modules.pop().quit()
+
+  @staticmethod
+  def _create_api_server(request_data, storage_path, options, configuration):
     datastore_path = options.datastore_path or os.path.join(storage_path,
                                                             'datastore.db')
     logs_path = options.logs_path or os.path.join(storage_path, 'logs.db')
-    xsrf_path = os.path.join(storage_path, 'xsrf')
 
     search_index_path = options.search_indexes_path or os.path.join(
         storage_path, 'search_indexes')
@@ -684,25 +799,38 @@
         user_logout_url=user_logout_url,
         default_gcs_bucket_name=options.default_gcs_bucket_name)
 
-    # The APIServer must bind to localhost because that is what the runtime
-    # instances talk to.
-    apis = api_server.APIServer('localhost', options.api_port,
+    return api_server.APIServer(options.api_host, options.api_port,
                                 configuration.app_id)
-    apis.start()
-    self._running_modules.append(apis)
 
-    self._running_modules.append(self._dispatcher)
-    self._dispatcher.start(apis.port, request_data)
+  @staticmethod
+  def _create_php_config(options):
+    php_config = runtime_config_pb2.PhpConfig()
+    if options.php_executable_path:
+      php_config.php_executable_path = os.path.abspath(
+          options.php_executable_path)
+    php_config.enable_debugger = options.php_remote_debugging
+    return php_config
 
-    admin = admin_server.AdminServer(options.admin_host, options.admin_port,
-                                     self._dispatcher, configuration, xsrf_path)
-    admin.start()
-    self._running_modules.append(admin)
+  @staticmethod
+  def _create_python_config(options):
+    python_config = runtime_config_pb2.PythonConfig()
+    if options.python_startup_script:
+      python_config.startup_script = os.path.abspath(
+          options.python_startup_script)
+      if options.python_startup_args:
+        python_config.startup_args = options.python_startup_args
+    return python_config
 
-  def stop(self):
-    """Stops all running devappserver2 modules."""
-    while self._running_modules:
-      self._running_modules.pop().quit()
+  @staticmethod
+  def _create_cloud_sql_config(options):
+    cloud_sql_config = runtime_config_pb2.CloudSQL()
+    cloud_sql_config.mysql_host = options.mysql_host
+    cloud_sql_config.mysql_port = options.mysql_port
+    cloud_sql_config.mysql_user = options.mysql_user
+    cloud_sql_config.mysql_password = options.mysql_password
+    if options.mysql_socket:
+      cloud_sql_config.mysql_socket = options.mysql_socket
+    return cloud_sql_config
 
 
 def main():
diff --git a/google/appengine/tools/devappserver2/devappserver2_test.py b/google/appengine/tools/devappserver2/devappserver2_test.py
index 885909f..f439342 100644
--- a/google/appengine/tools/devappserver2/devappserver2_test.py
+++ b/google/appengine/tools/devappserver2/devappserver2_test.py
@@ -187,12 +187,14 @@
     self.assertEqual(1, devappserver2.parse_max_module_instances('1'))
 
   def test_single_zero_arg(self):
-    self.assertRaises(argparse.ArgumentTypeError,
-                      devappserver2.parse_max_module_instances, '0')
+    self.assertRaisesRegexp(argparse.ArgumentTypeError,
+                            'Cannot specify zero instances for all',
+                            devappserver2.parse_max_module_instances, '0')
 
   def test_single_nonint_arg(self):
-    self.assertRaises(argparse.ArgumentTypeError,
-                      devappserver2.parse_max_module_instances, 'cat')
+    self.assertRaisesRegexp(argparse.ArgumentTypeError,
+                            'Invalid instance count:',
+                            devappserver2.parse_max_module_instances, 'cat')
 
   def test_multiple_valid_args(self):
     self.assertEqual(
@@ -201,23 +203,27 @@
         devappserver2.parse_max_module_instances('default:10,foo:5'))
 
   def test_multiple_non_colon(self):
-    self.assertRaises(
+    self.assertRaisesRegexp(
         argparse.ArgumentTypeError,
+        'Expected "module:max_instances"',
         devappserver2.parse_max_module_instances, 'default:10,foo')
 
   def test_multiple_non_int(self):
-    self.assertRaises(
+    self.assertRaisesRegexp(
         argparse.ArgumentTypeError,
+        'Expected "module:max_instances"',
         devappserver2.parse_max_module_instances, 'default:cat')
 
   def test_duplicate_modules(self):
-    self.assertRaises(
+    self.assertRaisesRegexp(
         argparse.ArgumentTypeError,
+        'Duplicate max instance value',
         devappserver2.parse_max_module_instances, 'default:5,default:10')
 
   def test_multiple_with_zero(self):
-    self.assertRaises(
+    self.assertRaisesRegexp(
         argparse.ArgumentTypeError,
+        'Cannot specify zero instances for module',
         devappserver2.parse_max_module_instances, 'default:5,foo:0')
 
   def test_multiple_missing_name(self):
@@ -226,9 +232,50 @@
         devappserver2.parse_max_module_instances(':10'))
 
   def test_multiple_missing_value(self):
-    self.assertRaises(
+    self.assertRaisesRegexp(
         argparse.ArgumentTypeError,
+        'Expected "module:max_instances"',
         devappserver2.parse_max_module_instances, 'default:')
 
+
+class ParseThreadsafeOverrideTest(unittest.TestCase):
+
+  def test_single_valid_arg(self):
+    self.assertTrue(devappserver2.parse_threadsafe_override('True'))
+    self.assertFalse(devappserver2.parse_threadsafe_override('No'))
+
+  def test_single_nonbool_art(self):
+    self.assertRaisesRegexp(
+        argparse.ArgumentTypeError, 'Invalid threadsafe override',
+        devappserver2.parse_threadsafe_override, 'okaydokey')
+
+  def test_multiple_valid_args(self):
+    self.assertEqual(
+        {'default': False,
+         'foo': True},
+        devappserver2.parse_threadsafe_override('default:False,foo:True'))
+
+  def test_multiple_non_colon(self):
+    self.assertRaisesRegexp(
+        argparse.ArgumentTypeError, 'Expected "module:threadsafe_override"',
+        devappserver2.parse_threadsafe_override, 'default:False,foo')
+
+  def test_multiple_non_int(self):
+    self.assertRaisesRegexp(
+        argparse.ArgumentTypeError, 'Expected "module:threadsafe_override"',
+        devappserver2.parse_threadsafe_override, 'default:okaydokey')
+
+  def test_duplicate_modules(self):
+    self.assertRaisesRegexp(
+        argparse.ArgumentTypeError,
+        'Duplicate threadsafe override value',
+        devappserver2.parse_threadsafe_override, 'default:False,default:True')
+
+  def test_multiple_missing_name(self):
+    self.assertEqual(
+        {'default': False},
+        devappserver2.parse_threadsafe_override(':No'))
+
+
 if __name__ == '__main__':
   unittest.main()
diff --git a/google/appengine/tools/devappserver2/dispatcher.py b/google/appengine/tools/devappserver2/dispatcher.py
index d64d5f7..e40fce6 100644
--- a/google/appengine/tools/devappserver2/dispatcher.py
+++ b/google/appengine/tools/devappserver2/dispatcher.py
@@ -64,14 +64,14 @@
                port,
                auth_domain,
                runtime_stderr_loglevel,
-               php_executable_path,
-               enable_php_remote_debugging,
+               php_config,
                python_config,
                cloud_sql_config,
                module_to_max_instances,
                use_mtime_file_watcher,
                automatic_restart,
-               allow_skipped_files):
+               allow_skipped_files,
+               module_to_threadsafe_override):
     """Initializer for Dispatcher.
 
     Args:
@@ -85,10 +85,8 @@
       runtime_stderr_loglevel: An int reprenting the minimum logging level at
           which runtime log messages should be written to stderr. See
           devappserver2.py for possible values.
-      php_executable_path: A string containing the path to PHP execution e.g.
-          "/usr/bin/php-cgi".
-      enable_php_remote_debugging: A boolean indicating whether the PHP
-          interpreter should be started with XDebug remote debugging enabled.
+      php_config: A runtime_config_pb2.PhpConfig instances containing PHP
+          runtime-specific configuration. If None then defaults are used.
       python_config: A runtime_config_pb2.PythonConfig instance containing
           Python runtime-specific configuration. If None then defaults are
           used.
@@ -107,13 +105,16 @@
       allow_skipped_files: If True then all files in the application's directory
           are readable, even if they appear in a static handler or "skip_files"
           directive.
+      module_to_threadsafe_override: A mapping between the module name and what
+        to override the module's YAML threadsafe configuration (so modules
+        not named continue to use their YAML configuration).
     """
     self._configuration = configuration
-    self._php_executable_path = php_executable_path
-    self._enable_php_remote_debugging = enable_php_remote_debugging
+    self._php_config = php_config
     self._python_config = python_config
     self._cloud_sql_config = cloud_sql_config
     self._request_data = None
+    self._api_host = None
     self._api_port = None
     self._running_modules = []
     self._module_configurations = {}
@@ -130,17 +131,20 @@
     self._use_mtime_file_watcher = use_mtime_file_watcher
     self._automatic_restart = automatic_restart
     self._allow_skipped_files = allow_skipped_files
+    self._module_to_threadsafe_override = module_to_threadsafe_override
     self._executor = scheduled_executor.ScheduledExecutor(_THREAD_POOL)
     self._port_registry = PortRegistry()
 
-  def start(self, api_port, request_data):
+  def start(self, api_host, api_port, request_data):
     """Starts the configured modules.
 
     Args:
+      api_host: The hostname that APIServer listens for RPC requests on.
       api_port: The port that APIServer listens for RPC requests on.
       request_data: A wsgi_request_info.WSGIRequestInfo that will be provided
           with request information for use by API stubs.
     """
+    self._api_host = api_host
     self._api_port = api_port
     self._request_data = request_data
     port = self._port
@@ -204,14 +208,16 @@
   def _create_module(self, module_configuration, port):
     max_instances = self._module_to_max_instances.get(
         module_configuration.module_name)
+    threadsafe_override = self._module_to_threadsafe_override.get(
+        module_configuration.module_name)
     module_args = (module_configuration,
                    self._host,
                    port,
+                   self._api_host,
                    self._api_port,
                    self._auth_domain,
                    self._runtime_stderr_loglevel,
-                   self._php_executable_path,
-                   self._enable_php_remote_debugging,
+                   self._php_config,
                    self._python_config,
                    self._cloud_sql_config,
                    self._port,
@@ -221,7 +227,8 @@
                    max_instances,
                    self._use_mtime_file_watcher,
                    self._automatic_restart,
-                   self._allow_skipped_files)
+                   self._allow_skipped_files,
+                   threadsafe_override)
     if module_configuration.manual_scaling:
       _module = module.ManualScalingModule(*module_args)
     elif module_configuration.basic_scaling:
diff --git a/google/appengine/tools/devappserver2/dispatcher_test.py b/google/appengine/tools/devappserver2/dispatcher_test.py
index 9d098e4..e44c9a4 100644
--- a/google/appengine/tools/devappserver2/dispatcher_test.py
+++ b/google/appengine/tools/devappserver2/dispatcher_test.py
@@ -90,11 +90,11 @@
         module_configuration,
         host,
         balanced_port,
+        api_host='localhost',
         api_port=8080,
         auth_domain='gmail.com',
         runtime_stderr_loglevel=1,
-        php_executable_path='/usr/bin/php-cgi',
-        enable_php_remote_debugging=False,
+        php_config=None,
         python_config=None,
         cloud_sql_config=None,
         default_version_port=8080,
@@ -104,7 +104,8 @@
         max_instances=None,
         use_mtime_file_watcher=False,
         automatic_restarts=True,
-        allow_skipped_files=False)
+        allow_skipped_files=False,
+        threadsafe_override=None)
 
   def start(self):
     pass
@@ -130,11 +131,11 @@
         module_configuration,
         host,
         balanced_port,
+        api_host='localhost',
         api_port=8080,
         auth_domain='gmail.com',
         runtime_stderr_loglevel=1,
-        php_executable_path='/usr/bin/php-cgi',
-        enable_php_remote_debugging=False,
+        php_config=None,
         python_config=None,
         cloud_sql_config=None,
         default_version_port=8080,
@@ -144,7 +145,8 @@
         max_instances=None,
         use_mtime_file_watcher=False,
         automatic_restarts=True,
-        allow_skipped_files=False)
+        allow_skipped_files=False,
+        threadsafe_override=None)
 
   def start(self):
     pass
@@ -179,14 +181,14 @@
         1,
         'gmail.com',
         1,
-        'php_executable_path',
-        'enable_php_remote_debugging',
+        php_config=None,
         python_config=None,
         cloud_sql_config=None,
         module_to_max_instances={},
         use_mtime_file_watcher=False,
         automatic_restart=True,
-        allow_skipped_files=False)
+        allow_skipped_files=False,
+        module_to_threadsafe_override={})
     self.module1 = AutoScalingModuleFacade(app_config.modules[0],
                                            balanced_port=1,
                                            host='localhost')
@@ -200,7 +202,7 @@
     self.dispatcher._create_module(app_config.modules[1], 2).AndReturn(
         (self.module2, 3))
     self.mox.ReplayAll()
-    self.dispatcher.start(12345, object())
+    self.dispatcher.start('localhost', 12345, object())
     app_config.dispatch = self.dispatch_config
     self.mox.VerifyAll()
     self.mox.StubOutWithMock(module.Module, 'build_request_environ')
diff --git a/google/appengine/tools/devappserver2/endpoints/testdata/app.yaml b/google/appengine/tools/devappserver2/endpoints/testdata/app.yaml
index 402bcea..575d623 100644
--- a/google/appengine/tools/devappserver2/endpoints/testdata/app.yaml
+++ b/google/appengine/tools/devappserver2/endpoints/testdata/app.yaml
@@ -11,3 +11,6 @@
 libraries:
 - name: pycrypto
   version: "2.6"
+
+- name: endpoints
+  version: "1.0"
diff --git a/google/appengine/tools/devappserver2/endpoints/testdata/test_service.py b/google/appengine/tools/devappserver2/endpoints/testdata/test_service.py
index 76bb9aa..0ad8272 100644
--- a/google/appengine/tools/devappserver2/endpoints/testdata/test_service.py
+++ b/google/appengine/tools/devappserver2/endpoints/testdata/test_service.py
@@ -18,12 +18,11 @@
 
 import logging
 
+import endpoints
 from protorpc import message_types
 from protorpc import messages
 from protorpc import remote
 
-from google.appengine.ext import endpoints
-
 
 class TestRequest(messages.Message):
   """Simple ProtoRPC request, for testing."""
diff --git a/google/appengine/tools/devappserver2/file_watcher.py b/google/appengine/tools/devappserver2/file_watcher.py
index e4ecea7..c4f973f 100644
--- a/google/appengine/tools/devappserver2/file_watcher.py
+++ b/google/appengine/tools/devappserver2/file_watcher.py
@@ -41,13 +41,10 @@
       watcher.quit()
 
   def has_changes(self):
-    has_changes = False
-    for watcher in self._file_watchers:
-      # .has_changes() returns True if there has been any changes since the
-      # last call to .has_changes() so it must be called for every FileWatcher
-      # to prevent spurious change notifications on subsequent calls.
-      has_changes = watcher.has_changes() or has_changes
-    return has_changes
+    # .has_changes() returns True if there has been any changes since the
+    # last call to .has_changes() so it must be called for every FileWatcher
+    # to prevent spurious change notifications on subsequent calls.
+    return any([watcher.has_changes() for watcher in self._file_watchers])
 
 
 def get_file_watcher(directories, use_mtime_file_watcher):
diff --git a/google/appengine/tools/devappserver2/module.py b/google/appengine/tools/devappserver2/module.py
index 90bf85f..82ed3cc 100644
--- a/google/appengine/tools/devappserver2/module.py
+++ b/google/appengine/tools/devappserver2/module.py
@@ -251,17 +251,23 @@
     runtime_config = runtime_config_pb2.Config()
     runtime_config.app_id = self._module_configuration.application
     runtime_config.version_id = self._module_configuration.version_id
-    runtime_config.threadsafe = self._module_configuration.threadsafe or False
+    if self._threadsafe_override is None:
+      runtime_config.threadsafe = self._module_configuration.threadsafe or False
+    else:
+      runtime_config.threadsafe = self._threadsafe_override
     runtime_config.application_root = (
         self._module_configuration.application_root)
     if not self._allow_skipped_files:
       runtime_config.skip_files = str(self._module_configuration.skip_files)
       runtime_config.static_files = _static_files_regex_from_handlers(
           self._module_configuration.handlers)
+    runtime_config.api_host = self._api_host
     runtime_config.api_port = self._api_port
     runtime_config.stderr_log_level = self._runtime_stderr_loglevel
     runtime_config.datacenter = 'us1'
     runtime_config.auth_domain = self._auth_domain
+    if self._max_instances is not None:
+      runtime_config.max_instances = self._max_instances
 
     for library in self._module_configuration.normalized_libraries:
       runtime_config.libraries.add(name=library.name, version=library.version)
@@ -272,15 +278,12 @@
     if self._cloud_sql_config:
       runtime_config.cloud_sql_config.CopyFrom(self._cloud_sql_config)
 
-    if self._module_configuration.runtime == 'php':
-      if self._php_executable_path:
-        runtime_config.php_config.php_executable_path = (
-            self._php_executable_path)
-      runtime_config.php_config.enable_debugger = (
-          self._enable_php_remote_debugging)
+    if self._php_config and self._module_configuration.runtime == 'php':
+      runtime_config.php_config.CopyFrom(self._php_config)
     if (self._python_config and
         self._module_configuration.runtime.startswith('python')):
       runtime_config.python_config.CopyFrom(self._python_config)
+
     return runtime_config
 
   def _maybe_restart_instances(self, config_changed, file_changed):
@@ -337,11 +340,11 @@
                module_configuration,
                host,
                balanced_port,
+               api_host,
                api_port,
                auth_domain,
                runtime_stderr_loglevel,
-               php_executable_path,
-               enable_php_remote_debugging,
+               php_config,
                python_config,
                cloud_sql_config,
                default_version_port,
@@ -351,7 +354,8 @@
                max_instances,
                use_mtime_file_watcher,
                automatic_restarts,
-               allow_skipped_files):
+               allow_skipped_files,
+               threadsafe_override):
     """Initializer for Module.
 
     Args:
@@ -361,16 +365,15 @@
           e.g. "localhost".
       balanced_port: An int specifying the port where the balanced module for
           the pool should listen.
+      api_host: The host that APIModule listens for RPC requests on.
       api_port: The port that APIModule listens for RPC requests on.
       auth_domain: A string containing the auth domain to set in the environment
           variables.
       runtime_stderr_loglevel: An int reprenting the minimum logging level at
           which runtime log messages should be written to stderr. See
           devappserver2.py for possible values.
-      php_executable_path: A string containing the path to PHP execution e.g.
-          "/usr/bin/php-cgi".
-      enable_php_remote_debugging: A boolean indicating whether the PHP
-          interpreter should be started with XDebug remote debugging enabled.
+      php_config: A runtime_config_pb2.PhpConfig instances containing PHP
+          runtime-specific configuration. If None then defaults are used.
       python_config: A runtime_config_pb2.PythonConfig instance containing
           Python runtime-specific configuration. If None then defaults are
           used.
@@ -393,20 +396,23 @@
       allow_skipped_files: If True then all files in the application's directory
           are readable, even if they appear in a static handler or "skip_files"
           directive.
+      threadsafe_override: If not None, ignore the YAML file value of threadsafe
+          and use this value instead.
     """
     self._module_configuration = module_configuration
     self._name = module_configuration.module_name
     self._host = host
+    self._api_host = api_host
     self._api_port = api_port
     self._auth_domain = auth_domain
     self._runtime_stderr_loglevel = runtime_stderr_loglevel
     self._balanced_port = balanced_port
-    self._php_executable_path = php_executable_path
-    self._enable_php_remote_debugging = enable_php_remote_debugging
+    self._php_config = php_config
     self._python_config = python_config
     self._cloud_sql_config = cloud_sql_config
     self._request_data = request_data
     self._allow_skipped_files = allow_skipped_files
+    self._threadsafe_override = threadsafe_override
     self._dispatcher = dispatcher
     self._max_instances = max_instances
     self._automatic_restarts = automatic_restarts
@@ -734,11 +740,11 @@
       return InteractiveCommandModule(self._module_configuration,
                                       self._host,
                                       self._balanced_port,
+                                      self._api_host,
                                       self._api_port,
                                       self._auth_domain,
                                       self._runtime_stderr_loglevel,
-                                      self._php_executable_path,
-                                      self._enable_php_remote_debugging,
+                                      self._php_config,
                                       self._python_config,
                                       self._cloud_sql_config,
                                       self._default_version_port,
@@ -746,7 +752,8 @@
                                       self._request_data,
                                       self._dispatcher,
                                       self._use_mtime_file_watcher,
-                                      self._allow_skipped_files)
+                                      self._allow_skipped_files,
+                                      self._threadsafe_override)
     else:
       raise NotImplementedError('runtime does not support interactive commands')
 
@@ -836,11 +843,11 @@
                module_configuration,
                host,
                balanced_port,
+               api_host,
                api_port,
                auth_domain,
                runtime_stderr_loglevel,
-               php_executable_path,
-               enable_php_remote_debugging,
+               php_config,
                python_config,
                cloud_sql_config,
                default_version_port,
@@ -850,7 +857,8 @@
                max_instances,
                use_mtime_file_watcher,
                automatic_restarts,
-               allow_skipped_files):
+               allow_skipped_files,
+               threadsafe_override):
     """Initializer for AutoScalingModule.
 
     Args:
@@ -860,16 +868,15 @@
           e.g. "localhost".
       balanced_port: An int specifying the port where the balanced module for
           the pool should listen.
+      api_host: The host that APIServer listens for RPC requests on.
       api_port: The port that APIServer listens for RPC requests on.
       auth_domain: A string containing the auth domain to set in the environment
           variables.
       runtime_stderr_loglevel: An int reprenting the minimum logging level at
           which runtime log messages should be written to stderr. See
           devappserver2.py for possible values.
-      php_executable_path: A string containing the path to PHP execution e.g.
-          "/usr/bin/php-cgi".
-      enable_php_remote_debugging: A boolean indicating whether the PHP
-          interpreter should be started with XDebug remote debugging enabled.
+      php_config: A runtime_config_pb2.PhpConfig instances containing PHP
+          runtime-specific configuration. If None then defaults are used.
       python_config: A runtime_config_pb2.PythonConfig instance containing
           Python runtime-specific configuration. If None then defaults are
           used.
@@ -892,15 +899,17 @@
       allow_skipped_files: If True then all files in the application's directory
           are readable, even if they appear in a static handler or "skip_files"
           directive.
+      threadsafe_override: If not None, ignore the YAML file value of threadsafe
+          and use this value instead.
     """
     super(AutoScalingModule, self).__init__(module_configuration,
                                             host,
                                             balanced_port,
+                                            api_host,
                                             api_port,
                                             auth_domain,
                                             runtime_stderr_loglevel,
-                                            php_executable_path,
-                                            enable_php_remote_debugging,
+                                            php_config,
                                             python_config,
                                             cloud_sql_config,
                                             default_version_port,
@@ -910,7 +919,8 @@
                                             max_instances,
                                             use_mtime_file_watcher,
                                             automatic_restarts,
-                                            allow_skipped_files)
+                                            allow_skipped_files,
+                                            threadsafe_override)
 
     self._process_automatic_scaling(
         self._module_configuration.automatic_scaling)
@@ -1268,11 +1278,11 @@
                module_configuration,
                host,
                balanced_port,
+               api_host,
                api_port,
                auth_domain,
                runtime_stderr_loglevel,
-               php_executable_path,
-               enable_php_remote_debugging,
+               php_config,
                python_config,
                cloud_sql_config,
                default_version_port,
@@ -1282,7 +1292,8 @@
                max_instances,
                use_mtime_file_watcher,
                automatic_restarts,
-               allow_skipped_files):
+               allow_skipped_files,
+               threadsafe_override):
     """Initializer for ManualScalingModule.
 
     Args:
@@ -1292,16 +1303,15 @@
           e.g. "localhost".
       balanced_port: An int specifying the port where the balanced module for
           the pool should listen.
+      api_host: The host that APIServer listens for RPC requests on.
       api_port: The port that APIServer listens for RPC requests on.
       auth_domain: A string containing the auth domain to set in the environment
           variables.
       runtime_stderr_loglevel: An int reprenting the minimum logging level at
           which runtime log messages should be written to stderr. See
           devappserver2.py for possible values.
-      php_executable_path: A string containing the path to PHP execution e.g.
-          "/usr/bin/php-cgi".
-      enable_php_remote_debugging: A boolean indicating whether the PHP
-          interpreter should be started with XDebug remote debugging enabled.
+      php_config: A runtime_config_pb2.PhpConfig instances containing PHP
+          runtime-specific configuration. If None then defaults are used.
       python_config: A runtime_config_pb2.PythonConfig instance containing
           Python runtime-specific configuration. If None then defaults are
           used.
@@ -1324,15 +1334,17 @@
       allow_skipped_files: If True then all files in the application's directory
           are readable, even if they appear in a static handler or "skip_files"
           directive.
+      threadsafe_override: If not None, ignore the YAML file value of threadsafe
+          and use this value instead.
     """
     super(ManualScalingModule, self).__init__(module_configuration,
                                               host,
                                               balanced_port,
+                                              api_host,
                                               api_port,
                                               auth_domain,
                                               runtime_stderr_loglevel,
-                                              php_executable_path,
-                                              enable_php_remote_debugging,
+                                              php_config,
                                               python_config,
                                               cloud_sql_config,
                                               default_version_port,
@@ -1342,7 +1354,8 @@
                                               max_instances,
                                               use_mtime_file_watcher,
                                               automatic_restarts,
-                                              allow_skipped_files)
+                                              allow_skipped_files,
+                                              threadsafe_override)
 
     self._process_manual_scaling(module_configuration.manual_scaling)
 
@@ -1764,11 +1777,11 @@
                module_configuration,
                host,
                balanced_port,
+               api_host,
                api_port,
                auth_domain,
                runtime_stderr_loglevel,
-               php_executable_path,
-               enable_php_remote_debugging,
+               php_config,
                python_config,
                cloud_sql_config,
                default_version_port,
@@ -1778,7 +1791,8 @@
                max_instances,
                use_mtime_file_watcher,
                automatic_restarts,
-               allow_skipped_files):
+               allow_skipped_files,
+               threadsafe_override):
     """Initializer for BasicScalingModule.
 
     Args:
@@ -1788,16 +1802,15 @@
           e.g. "localhost".
       balanced_port: An int specifying the port where the balanced module for
           the pool should listen.
+      api_host: The host that APIServer listens for RPC requests on.
       api_port: The port that APIServer listens for RPC requests on.
       auth_domain: A string containing the auth domain to set in the environment
           variables.
       runtime_stderr_loglevel: An int reprenting the minimum logging level at
           which runtime log messages should be written to stderr. See
           devappserver2.py for possible values.
-      php_executable_path: A string containing the path to PHP execution e.g.
-          "/usr/bin/php-cgi".
-      enable_php_remote_debugging: A boolean indicating whether the PHP
-          interpreter should be started with XDebug remote debugging enabled.
+      php_config: A runtime_config_pb2.PhpConfig instances containing PHP
+          runtime-specific configuration. If None then defaults are used.
       python_config: A runtime_config_pb2.PythonConfig instance containing
           Python runtime-specific configuration. If None then defaults are
           used.
@@ -1820,15 +1833,17 @@
       allow_skipped_files: If True then all files in the application's directory
           are readable, even if they appear in a static handler or "skip_files"
           directive.
+      threadsafe_override: If not None, ignore the YAML file value of threadsafe
+          and use this value instead.
     """
     super(BasicScalingModule, self).__init__(module_configuration,
                                              host,
                                              balanced_port,
+                                             api_host,
                                              api_port,
                                              auth_domain,
                                              runtime_stderr_loglevel,
-                                             php_executable_path,
-                                             enable_php_remote_debugging,
+                                             php_config,
                                              python_config,
                                              cloud_sql_config,
                                              default_version_port,
@@ -1838,7 +1853,8 @@
                                              max_instances,
                                              use_mtime_file_watcher,
                                              automatic_restarts,
-                                             allow_skipped_files)
+                                             allow_skipped_files,
+                                             threadsafe_override)
     self._process_basic_scaling(module_configuration.basic_scaling)
 
     self._instances = []  # Protected by self._condition.
@@ -2186,11 +2202,11 @@
                module_configuration,
                host,
                balanced_port,
+               api_host,
                api_port,
                auth_domain,
                runtime_stderr_loglevel,
-               php_executable_path,
-               enable_php_remote_debugging,
+               php_config,
                python_config,
                cloud_sql_config,
                default_version_port,
@@ -2198,7 +2214,8 @@
                request_data,
                dispatcher,
                use_mtime_file_watcher,
-               allow_skipped_files):
+               allow_skipped_files,
+               threadsafe_override):
     """Initializer for InteractiveCommandModule.
 
     Args:
@@ -2210,16 +2227,15 @@
       balanced_port: An int specifying the port that will be used when
           constructing HTTP headers sent to the Instance executing the
           interactive command e.g. "localhost".
+      api_host: The host that APIServer listens for RPC requests on.
       api_port: The port that APIServer listens for RPC requests on.
       auth_domain: A string containing the auth domain to set in the environment
           variables.
       runtime_stderr_loglevel: An int reprenting the minimum logging level at
           which runtime log messages should be written to stderr. See
           devappserver2.py for possible values.
-      php_executable_path: A string containing the path to PHP execution e.g.
-          "/usr/bin/php-cgi".
-      enable_php_remote_debugging: A boolean indicating whether the PHP
-          interpreter should be started with XDebug remote debugging enabled.
+      php_config: A runtime_config_pb2.PhpConfig instances containing PHP
+          runtime-specific configuration. If None then defaults are used.
       python_config: A runtime_config_pb2.PythonConfig instance containing
           Python runtime-specific configuration. If None then defaults are
           used.
@@ -2238,16 +2254,18 @@
       allow_skipped_files: If True then all files in the application's directory
           are readable, even if they appear in a static handler or "skip_files"
           directive.
+      threadsafe_override: If not None, ignore the YAML file value of threadsafe
+          and use this value instead.
     """
     super(InteractiveCommandModule, self).__init__(
         module_configuration,
         host,
         balanced_port,
+        api_host,
         api_port,
         auth_domain,
         runtime_stderr_loglevel,
-        php_executable_path,
-        enable_php_remote_debugging,
+        php_config,
         python_config,
         cloud_sql_config,
         default_version_port,
@@ -2257,7 +2275,8 @@
         max_instances=1,
         use_mtime_file_watcher=use_mtime_file_watcher,
         automatic_restarts=True,
-        allow_skipped_files=allow_skipped_files)
+        allow_skipped_files=allow_skipped_files,
+        threadsafe_override=threadsafe_override)
     # Use a single instance so that state is consistent across requests.
     self._inst_lock = threading.Lock()
     self._inst = None
diff --git a/google/appengine/tools/devappserver2/module_test.py b/google/appengine/tools/devappserver2/module_test.py
index 0913e55..62d86e9 100644
--- a/google/appengine/tools/devappserver2/module_test.py
+++ b/google/appengine/tools/devappserver2/module_test.py
@@ -82,16 +82,17 @@
                module_configuration=ModuleConfigurationStub(),
                instance_factory=None,
                ready=True,
-               allow_skipped_files=False):
+               allow_skipped_files=False,
+               threadsafe_override=None):
     super(ModuleFacade, self).__init__(
         module_configuration,
         host='fakehost',
         balanced_port=0,
+        api_host='localhost',
         api_port=8080,
         auth_domain='gmail.com',
         runtime_stderr_loglevel=1,
-        php_executable_path='/usr/bin/php-cgi',
-        enable_php_remote_debugging=False,
+        php_config=None,
         python_config=None,
         cloud_sql_config=None,
         default_version_port=8080,
@@ -101,7 +102,8 @@
         max_instances=None,
         use_mtime_file_watcher=False,
         automatic_restarts=True,
-        allow_skipped_files=allow_skipped_files)
+        allow_skipped_files=allow_skipped_files,
+        threadsafe_override=threadsafe_override)
     if instance_factory is not None:
       self._instance_factory = instance_factory
     self._ready = ready
@@ -126,11 +128,11 @@
         module_configuration,
         host='fakehost',
         balanced_port=balanced_port,
+        api_host='localhost',
         api_port=8080,
         auth_domain='gmail.com',
         runtime_stderr_loglevel=1,
-        php_executable_path='/usr/bin/php-cgi',
-        enable_php_remote_debugging=False,
+        php_config=None,
         python_config=None,
         cloud_sql_config=None,
         default_version_port=8080,
@@ -140,7 +142,8 @@
         max_instances=max_instances,
         use_mtime_file_watcher=False,
         automatic_restarts=True,
-        allow_skipped_files=False)
+        allow_skipped_files=False,
+        threadsafe_override=None)
     if instance_factory is not None:
       self._instance_factory = instance_factory
     self._ready = ready
@@ -164,11 +167,11 @@
         module_configuration,
         host='fakehost',
         balanced_port=balanced_port,
+        api_host='localhost',
         api_port=8080,
         auth_domain='gmail.com',
         runtime_stderr_loglevel=1,
-        php_executable_path='/usr/bin/php-cgi',
-        enable_php_remote_debugging=False,
+        php_config=None,
         python_config=None,
         cloud_sql_config=None,
         default_version_port=8080,
@@ -178,7 +181,8 @@
         max_instances=None,
         use_mtime_file_watcher=False,
         automatic_restarts=True,
-        allow_skipped_files=False)
+        allow_skipped_files=False,
+        threadsafe_override=None)
     if instance_factory is not None:
       self._instance_factory = instance_factory
     self._ready = ready
@@ -203,11 +207,11 @@
         module_configuration,
         host,
         balanced_port=balanced_port,
+        api_host='localhost',
         api_port=8080,
         auth_domain='gmail.com',
         runtime_stderr_loglevel=1,
-        php_executable_path='/usr/bin/php-cgi',
-        enable_php_remote_debugging=False,
+        php_config=None,
         python_config=None,
         cloud_sql_config=None,
         default_version_port=8080,
@@ -217,7 +221,8 @@
         max_instances=None,
         use_mtime_file_watcher=False,
         automatic_restarts=True,
-        allow_skipped_files=False)
+        allow_skipped_files=False,
+        threadsafe_override=None)
     if instance_factory is not None:
       self._instance_factory = instance_factory
     self._ready = ready
@@ -427,6 +432,36 @@
     self.assertFalse(config.HasField('skip_files'))
     self.assertFalse(config.HasField('static_files'))
 
+  def test_threadsafe_true_override_none(self):
+    self.module_configuration.threadsafe = True
+    servr = ModuleFacade(instance_factory=self.instance_factory,
+                         module_configuration=self.module_configuration)
+    config = servr._get_runtime_config()
+    self.assertTrue(config.threadsafe)
+
+  def test_threadsafe_false_override_none(self):
+    self.module_configuration.threadsafe = False
+    servr = ModuleFacade(instance_factory=self.instance_factory,
+                         module_configuration=self.module_configuration)
+    config = servr._get_runtime_config()
+    self.assertFalse(config.threadsafe)
+
+  def test_threadsafe_true_override_false(self):
+    self.module_configuration.threadsafe = True
+    servr = ModuleFacade(instance_factory=self.instance_factory,
+                         module_configuration=self.module_configuration,
+                         threadsafe_override=False)
+    config = servr._get_runtime_config()
+    self.assertFalse(config.threadsafe)
+
+  def test_threadsafe_false_override_true(self):
+    self.module_configuration.threadsafe = False
+    servr = ModuleFacade(instance_factory=self.instance_factory,
+                         module_configuration=self.module_configuration,
+                         threadsafe_override=True)
+    config = servr._get_runtime_config()
+    self.assertTrue(config.threadsafe)
+
 
 class TestModuleShutdownInstance(unittest.TestCase):
   """Tests for module.Module._shutdown_instance."""
@@ -2226,11 +2261,11 @@
         ModuleConfigurationStub(),
         'fakehost',
         balanced_port=8000,
+        api_host='localhost',
         api_port=9000,
         auth_domain='gmail.com',
         runtime_stderr_loglevel=1,
-        php_executable_path='/usr/bin/php-cgi',
-        enable_php_remote_debugging=False,
+        php_config=None,
         python_config=None,
         cloud_sql_config=None,
         default_version_port=8080,
@@ -2238,7 +2273,8 @@
         request_data=None,
         dispatcher=None,
         use_mtime_file_watcher=False,
-        allow_skipped_files=False)
+        allow_skipped_files=False,
+        threadsafe_override=None)
     self.mox.StubOutWithMock(self.servr._instance_factory, 'new_instance')
     self.mox.StubOutWithMock(self.servr, '_handle_request')
     self.mox.StubOutWithMock(self.servr, 'build_request_environ')
diff --git a/google/appengine/tools/devappserver2/php/runtime.py b/google/appengine/tools/devappserver2/php/runtime.py
index f328ce5..ddbcec1 100644
--- a/google/appengine/tools/devappserver2/php/runtime.py
+++ b/google/appengine/tools/devappserver2/php/runtime.py
@@ -37,7 +37,13 @@
 from google.appengine.tools.devappserver2 import wsgi_server
 
 SDK_PATH = os.path.abspath(
-    os.path.join(os.path.dirname(sys.argv[0]), 'php/sdk'))
+    os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), 'php/sdk'))
+
+
+if not os.path.exists(SDK_PATH):
+  SDK_PATH = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]),
+                                          'php/sdk'))
+
 SETUP_PHP_PATH = os.path.join(os.path.dirname(php.__file__), 'setup.php')
 
 
@@ -65,6 +71,7 @@
         # REDIRECT_STATUS must be set. See:
         # http://php.net/manual/en/security.cgi-bin.force-redirect.php
         'REDIRECT_STATUS': '1',
+        'REMOTE_API_HOST': str(config.api_host),
         'REMOTE_API_PORT': str(config.api_port),
         'SERVER_SOFTWARE': http_runtime_constants.SERVER_SOFTWARE,
         'TZ': 'UTC',
@@ -138,6 +145,11 @@
       args.extend(['-d', 'xdebug.remote_enable="1"'])
       user_environ['XDEBUG_CONFIG'] = os.environ.get('XDEBUG_CONFIG', '')
 
+    request_type = environ.pop(http_runtime_constants.REQUEST_TYPE_HEADER, None)
+    if request_type == 'interactive':
+      args.extend(['-d', 'html_errors="0"'])
+      user_environ[http_runtime_constants.REQUEST_TYPE_HEADER] = request_type
+
     try:
       p = subprocess.Popen(args,
                            stdin=subprocess.PIPE,
@@ -153,12 +165,17 @@
       return ['Failure to start the PHP subprocess with %r:\n%s' % (args, e)]
 
     if p.returncode:
-      logging.error('php failure (%r) with:\nstdout:\n%sstderr:\n%s',
-                    p.returncode, stdout, stderr)
-      start_response('500 Internal Server Error',
-                     [(http_runtime_constants.ERROR_CODE_HEADER, '1')])
-      return ['php failure (%r) with:\nstdout:%s\nstderr:\n%s' %
-              (p.returncode, stdout, stderr)]
+      if request_type == 'interactive':
+        start_response('200 OK', [('Content-Type', 'text/plain')])
+        message = httplib.HTTPMessage(cStringIO.StringIO(stdout))
+        return [message.fp.read()]
+      else:
+        logging.error('php failure (%r) with:\nstdout:\n%sstderr:\n%s',
+                      p.returncode, stdout, stderr)
+        start_response('500 Internal Server Error',
+                       [(http_runtime_constants.ERROR_CODE_HEADER, '1')])
+        return ['php failure (%r) with:\nstdout:%s\nstderr:\n%s' %
+                (p.returncode, stdout, stderr)]
 
     message = httplib.HTTPMessage(cStringIO.StringIO(stdout))
     assert 'Content-Type' in message, 'invalid CGI response: %r' % stdout
diff --git a/google/appengine/tools/devappserver2/php/setup.php b/google/appengine/tools/devappserver2/php/setup.php
index 973c95e..e032660 100644
--- a/google/appengine/tools/devappserver2/php/setup.php
+++ b/google/appengine/tools/devappserver2/php/setup.php
@@ -52,11 +52,15 @@
     require_once 'google/appengine/runtime/RemoteApiProxy.php';
     \google\appengine\runtime\ApiProxy::setApiProxy(
       new \google\appengine\runtime\RemoteApiProxy(
-        getenv('REMOTE_API_PORT'), getenv('REMOTE_REQUEST_ID')));
+        getenv('REMOTE_API_HOST'), getenv('REMOTE_API_PORT'),
+        getenv('REMOTE_REQUEST_ID')));
+    putenv('REMOTE_API_HOST');
     putenv('REMOTE_API_PORT');
     putenv('REMOTE_REQUEST_ID');
+    unset($_SERVER['REMOTE_API_HOST']);
     unset($_SERVER['REMOTE_API_PORT']);
     unset($_SERVER['REMOTE_REQUEST_ID']);
+    unset($_ENV['REMOTE_API_HOST']);
     unset($_ENV['REMOTE_API_PORT']);
     unset($_ENV['REMOTE_REQUEST_ID']);
   };
@@ -72,6 +76,28 @@
 };
 $setup();
 unset($setup);
-// Use require rather than include so a missing script produces a fatal error
-// instead of a warning.
-require($_ENV['SCRIPT_FILENAME']);
+
+$checkInteractive = function() {
+  if (isset($_ENV['HTTP_X_APPENGINE_INTERNAL_REQUEST_TYPE'])) {
+    $request_type = $_ENV['HTTP_X_APPENGINE_INTERNAL_REQUEST_TYPE'];
+    putenv('HTTP_X_APPENGINE_INTERNAL_REQUEST_TYPE');
+    unset($_SERVER['HTTP_X_APPENGINE_INTERNAL_REQUEST_TYPE']);
+    unset($_ENV['HTTP_X_APPENGINE_INTERNAL_REQUEST_TYPE']);
+    if ($request_type == 'interactive') {
+      return true;
+    }
+  }
+  return false;
+};
+
+if ($checkInteractive()) {
+  unset($checkInteractive);
+  eval(file_get_contents("php://input"));
+} else {
+  unset($checkInteractive);
+  // Use require rather than include so a missing script produces a fatal error
+  // instead of a warning.
+  require($_ENV['SCRIPT_FILENAME']);
+}
+
+
diff --git a/google/appengine/tools/devappserver2/php_runtime.py b/google/appengine/tools/devappserver2/php_runtime.py
index 84a9ae0..0662295 100644
--- a/google/appengine/tools/devappserver2/php_runtime.py
+++ b/google/appengine/tools/devappserver2/php_runtime.py
@@ -114,6 +114,7 @@
       url='/_ah/warmup',
       script='$PHP_LIB/default_warmup_handler',
       login='admin')
+  SUPPORTS_INTERACTIVE_REQUESTS = True
   FILE_CHANGE_INSTANCE_RESTART_POLICY = instance.NEVER
 
   def __init__(self, request_data, runtime_config_getter, module_configuration):
@@ -156,10 +157,6 @@
     if 'SYSTEMROOT' in os.environ:
       env['SYSTEMROOT'] = os.environ['SYSTEMROOT']
 
-    if not os.access(php_executable_path, os.X_OK):
-      raise _PHPBinaryError('The path specified with the --php_executable_path '
-                            'flag (%s) is not executable' % php_executable_path)
-
     version_process = safe_subprocess.start_process([php_executable_path, '-v'],
                                                     stdout=subprocess.PIPE,
                                                     stderr=subprocess.PIPE,
diff --git a/google/appengine/tools/devappserver2/python/pdb_sandbox.py b/google/appengine/tools/devappserver2/python/pdb_sandbox.py
index c911b96..93f5854 100644
--- a/google/appengine/tools/devappserver2/python/pdb_sandbox.py
+++ b/google/appengine/tools/devappserver2/python/pdb_sandbox.py
@@ -17,13 +17,17 @@
 """Modify pdb to work with the devappserver2 sandbox."""
 
 import sys
+import threading
 
-def install():
+def install(config):
   """Install the necessary changes to pdb.
 
   Monkeypatch pdb so that it can be used in the devappserver sandbox. Must
   be called after the sandbox has been installed but before stdin/stdout
   objects have been reassigned.
+
+  Args:
+    config: The runtime_config_pb2.Config to use to configure the sandbox.
   """
   # Import here (i.e. after sandbox installed) to get the post sandbox pdb.
   # Extremely important so that we monkeypatch the same pdb the apps can
@@ -40,7 +44,21 @@
   # recursion).
   pdb_premonkeypatch = pdb_postsandbox.Pdb
 
+  if config.threadsafe or config.max_instances != 1:
+    warning = """
+********************************************************************************
+* WARNING: please read before using PDB:
+* https://developers.google.com/appengine/docs/python/tools/devserver#Python_Debugging_with_PDB
+********************************************************************************
+
+"""
+    lock = threading.Lock()
+  else:
+    warning = ''
+
   class _Pdb(pdb_postsandbox.Pdb):
+    _warning_written = False
+
     # TODO: improve argument handling so if new arguments are added
     # in the future or the defaults change, this does not need to be updated.
     def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None):
@@ -51,4 +69,12 @@
       # Pdb is old style class so no super().
       pdb_premonkeypatch.__init__(self, completekey, stdin, stdout, skip)
 
+      if warning:
+        with lock:
+          # Note: while the goal is to write the warning only one time, it
+          # may be written multiple times (once each per instance).
+          if not _Pdb._warning_written:
+            stdout.write(warning)
+            _Pdb._warning_written = True
+
   pdb_postsandbox.Pdb = _Pdb
diff --git a/google/appengine/tools/devappserver2/python/runtime.py b/google/appengine/tools/devappserver2/python/runtime.py
index bde0e02..c9a1f59 100644
--- a/google/appengine/tools/devappserver2/python/runtime.py
+++ b/google/appengine/tools/devappserver2/python/runtime.py
@@ -55,9 +55,10 @@
 
 def setup_stubs(config):
   """Sets up API stubs using remote API."""
-  remote_api_stub.ConfigureRemoteApi(config.app_id, '/', lambda: ('', ''),
-                                     'localhost:%d' % config.api_port,
-                                     use_remote_datastore=False)
+  remote_api_stub.ConfigureRemoteApi(
+      config.app_id, '/', lambda: ('', ''),
+      '%s:%d' % (str(config.api_host), config.api_port),
+      use_remote_datastore=False)
 
   if config.HasField('cloud_sql_config'):
     # Connect the RDBMS API to MySQL.
diff --git a/google/appengine/tools/devappserver2/python/runtime_test.py b/google/appengine/tools/devappserver2/python/runtime_test.py
index 5796de1..31f564c 100644
--- a/google/appengine/tools/devappserver2/python/runtime_test.py
+++ b/google/appengine/tools/devappserver2/python/runtime_test.py
@@ -38,10 +38,11 @@
   def test_setup_stubs(self):
     self.mox.StubOutWithMock(remote_api_stub, 'ConfigureRemoteApi')
     remote_api_stub.ConfigureRemoteApi('app', '/', mox.IgnoreArg(),
-                                       'localhost:12345',
+                                       'somehost:12345',
                                        use_remote_datastore=False)
     config = runtime_config_pb2.Config()
     config.app_id = 'app'
+    config.api_host = 'somehost'
     config.api_port = 12345
     self.mox.ReplayAll()
     runtime.setup_stubs(config)
diff --git a/google/appengine/tools/devappserver2/python/sandbox.py b/google/appengine/tools/devappserver2/python/sandbox.py
index 8fad0cf..688670c 100644
--- a/google/appengine/tools/devappserver2/python/sandbox.py
+++ b/google/appengine/tools/devappserver2/python/sandbox.py
@@ -173,7 +173,7 @@
   request_environment.PatchOsEnviron(sandboxed_os)
   os.__dict__.update(sandboxed_os.__dict__)
   _init_logging(config.stderr_log_level)
-  pdb_sandbox.install()
+  pdb_sandbox.install(config)
   sys.stdin = devnull
   sys.stdout = sys.stderr
 
diff --git a/google/appengine/tools/devappserver2/runtime_config_pb2.py b/google/appengine/tools/devappserver2/runtime_config_pb2.py
index 76a11e5..726ba3d 100644
--- a/google/appengine/tools/devappserver2/runtime_config_pb2.py
+++ b/google/appengine/tools/devappserver2/runtime_config_pb2.py
@@ -29,7 +29,7 @@
 DESCRIPTOR = _descriptor.FileDescriptor(
   name='apphosting/tools/devappserver2/runtime_config.proto',
   package='apphosting.tools.devappserver2',
-  serialized_pb='\n3apphosting/tools/devappserver2/runtime_config.proto\x12\x1e\x61pphosting.tools.devappserver2\"\xbe\x04\n\x06\x43onfig\x12\x0e\n\x06\x61pp_id\x18\x01 \x02(\x0c\x12\x12\n\nversion_id\x18\x02 \x02(\x0c\x12\x18\n\x10\x61pplication_root\x18\x03 \x02(\x0c\x12\x19\n\nthreadsafe\x18\x04 \x01(\x08:\x05\x66\x61lse\x12\x10\n\x08\x61pi_port\x18\x05 \x02(\x05\x12:\n\tlibraries\x18\x06 \x03(\x0b\x32\'.apphosting.tools.devappserver2.Library\x12\x16\n\nskip_files\x18\x07 \x01(\t:\x02^$\x12\x18\n\x0cstatic_files\x18\x08 \x01(\t:\x02^$\x12\x43\n\rpython_config\x18\x0e \x01(\x0b\x32,.apphosting.tools.devappserver2.PythonConfig\x12=\n\nphp_config\x18\t \x01(\x0b\x32).apphosting.tools.devappserver2.PhpConfig\x12\x38\n\x07\x65nviron\x18\n \x03(\x0b\x32\'.apphosting.tools.devappserver2.Environ\x12\x42\n\x10\x63loud_sql_config\x18\x0b \x01(\x0b\x32(.apphosting.tools.devappserver2.CloudSQL\x12\x12\n\ndatacenter\x18\x0c \x02(\t\x12\x13\n\x0binstance_id\x18\r \x02(\t\x12\x1b\n\x10stderr_log_level\x18\x0f \x01(\x03:\x01\x31\x12\x13\n\x0b\x61uth_domain\x18\x10 \x02(\t\"A\n\tPhpConfig\x12\x1b\n\x13php_executable_path\x18\x01 \x01(\x0c\x12\x17\n\x0f\x65nable_debugger\x18\x03 \x02(\x08\"<\n\x0cPythonConfig\x12\x16\n\x0estartup_script\x18\x01 \x01(\t\x12\x14\n\x0cstartup_args\x18\x02 \x01(\t\"t\n\x08\x43loudSQL\x12\x12\n\nmysql_host\x18\x01 \x02(\t\x12\x12\n\nmysql_port\x18\x02 \x02(\x05\x12\x12\n\nmysql_user\x18\x03 \x02(\t\x12\x16\n\x0emysql_password\x18\x04 \x02(\t\x12\x14\n\x0cmysql_socket\x18\x05 \x01(\t\"(\n\x07Library\x12\x0c\n\x04name\x18\x01 \x02(\t\x12\x0f\n\x07version\x18\x02 \x02(\t\"%\n\x07\x45nviron\x12\x0b\n\x03key\x18\x01 \x02(\x0c\x12\r\n\x05value\x18\x02 \x02(\x0c\x42\x02 \x02')
+  serialized_pb='\n3apphosting/tools/devappserver2/runtime_config.proto\x12\x1e\x61pphosting.tools.devappserver2\"\xf2\x04\n\x06\x43onfig\x12\x0e\n\x06\x61pp_id\x18\x01 \x02(\x0c\x12\x12\n\nversion_id\x18\x02 \x02(\x0c\x12\x18\n\x10\x61pplication_root\x18\x03 \x02(\x0c\x12\x19\n\nthreadsafe\x18\x04 \x01(\x08:\x05\x66\x61lse\x12\x1b\n\x08\x61pi_host\x18\x11 \x01(\t:\tlocalhost\x12\x10\n\x08\x61pi_port\x18\x05 \x02(\x05\x12:\n\tlibraries\x18\x06 \x03(\x0b\x32\'.apphosting.tools.devappserver2.Library\x12\x16\n\nskip_files\x18\x07 \x01(\t:\x02^$\x12\x18\n\x0cstatic_files\x18\x08 \x01(\t:\x02^$\x12\x43\n\rpython_config\x18\x0e \x01(\x0b\x32,.apphosting.tools.devappserver2.PythonConfig\x12=\n\nphp_config\x18\t \x01(\x0b\x32).apphosting.tools.devappserver2.PhpConfig\x12\x38\n\x07\x65nviron\x18\n \x03(\x0b\x32\'.apphosting.tools.devappserver2.Environ\x12\x42\n\x10\x63loud_sql_config\x18\x0b \x01(\x0b\x32(.apphosting.tools.devappserver2.CloudSQL\x12\x12\n\ndatacenter\x18\x0c \x02(\t\x12\x13\n\x0binstance_id\x18\r \x02(\t\x12\x1b\n\x10stderr_log_level\x18\x0f \x01(\x03:\x01\x31\x12\x13\n\x0b\x61uth_domain\x18\x10 \x02(\t\x12\x15\n\rmax_instances\x18\x12 \x01(\x05\"A\n\tPhpConfig\x12\x1b\n\x13php_executable_path\x18\x01 \x01(\x0c\x12\x17\n\x0f\x65nable_debugger\x18\x03 \x02(\x08\"<\n\x0cPythonConfig\x12\x16\n\x0estartup_script\x18\x01 \x01(\t\x12\x14\n\x0cstartup_args\x18\x02 \x01(\t\"t\n\x08\x43loudSQL\x12\x12\n\nmysql_host\x18\x01 \x02(\t\x12\x12\n\nmysql_port\x18\x02 \x02(\x05\x12\x12\n\nmysql_user\x18\x03 \x02(\t\x12\x16\n\x0emysql_password\x18\x04 \x02(\t\x12\x14\n\x0cmysql_socket\x18\x05 \x01(\t\"(\n\x07Library\x12\x0c\n\x04name\x18\x01 \x02(\t\x12\x0f\n\x07version\x18\x02 \x02(\t\"%\n\x07\x45nviron\x12\x0b\n\x03key\x18\x01 \x02(\x0c\x12\r\n\x05value\x18\x02 \x02(\x0c\x42\x02 \x02')
 
 
 
@@ -70,89 +70,103 @@
       is_extension=False, extension_scope=None,
       options=None),
     _descriptor.FieldDescriptor(
-      name='api_port', full_name='apphosting.tools.devappserver2.Config.api_port', index=4,
+      name='api_host', full_name='apphosting.tools.devappserver2.Config.api_host', index=4,
+      number=17, type=9, cpp_type=9, label=1,
+      has_default_value=True, default_value=unicode("localhost", "utf-8"),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='api_port', full_name='apphosting.tools.devappserver2.Config.api_port', index=5,
       number=5, type=5, cpp_type=1, label=2,
       has_default_value=False, default_value=0,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
     _descriptor.FieldDescriptor(
-      name='libraries', full_name='apphosting.tools.devappserver2.Config.libraries', index=5,
+      name='libraries', full_name='apphosting.tools.devappserver2.Config.libraries', index=6,
       number=6, type=11, cpp_type=10, label=3,
       has_default_value=False, default_value=[],
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
     _descriptor.FieldDescriptor(
-      name='skip_files', full_name='apphosting.tools.devappserver2.Config.skip_files', index=6,
+      name='skip_files', full_name='apphosting.tools.devappserver2.Config.skip_files', index=7,
       number=7, type=9, cpp_type=9, label=1,
       has_default_value=True, default_value=unicode("^$", "utf-8"),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
     _descriptor.FieldDescriptor(
-      name='static_files', full_name='apphosting.tools.devappserver2.Config.static_files', index=7,
+      name='static_files', full_name='apphosting.tools.devappserver2.Config.static_files', index=8,
       number=8, type=9, cpp_type=9, label=1,
       has_default_value=True, default_value=unicode("^$", "utf-8"),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
     _descriptor.FieldDescriptor(
-      name='python_config', full_name='apphosting.tools.devappserver2.Config.python_config', index=8,
+      name='python_config', full_name='apphosting.tools.devappserver2.Config.python_config', index=9,
       number=14, type=11, cpp_type=10, label=1,
       has_default_value=False, default_value=None,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
     _descriptor.FieldDescriptor(
-      name='php_config', full_name='apphosting.tools.devappserver2.Config.php_config', index=9,
+      name='php_config', full_name='apphosting.tools.devappserver2.Config.php_config', index=10,
       number=9, type=11, cpp_type=10, label=1,
       has_default_value=False, default_value=None,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
     _descriptor.FieldDescriptor(
-      name='environ', full_name='apphosting.tools.devappserver2.Config.environ', index=10,
+      name='environ', full_name='apphosting.tools.devappserver2.Config.environ', index=11,
       number=10, type=11, cpp_type=10, label=3,
       has_default_value=False, default_value=[],
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
     _descriptor.FieldDescriptor(
-      name='cloud_sql_config', full_name='apphosting.tools.devappserver2.Config.cloud_sql_config', index=11,
+      name='cloud_sql_config', full_name='apphosting.tools.devappserver2.Config.cloud_sql_config', index=12,
       number=11, type=11, cpp_type=10, label=1,
       has_default_value=False, default_value=None,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
     _descriptor.FieldDescriptor(
-      name='datacenter', full_name='apphosting.tools.devappserver2.Config.datacenter', index=12,
+      name='datacenter', full_name='apphosting.tools.devappserver2.Config.datacenter', index=13,
       number=12, type=9, cpp_type=9, label=2,
       has_default_value=False, default_value=unicode("", "utf-8"),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
     _descriptor.FieldDescriptor(
-      name='instance_id', full_name='apphosting.tools.devappserver2.Config.instance_id', index=13,
+      name='instance_id', full_name='apphosting.tools.devappserver2.Config.instance_id', index=14,
       number=13, type=9, cpp_type=9, label=2,
       has_default_value=False, default_value=unicode("", "utf-8"),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
     _descriptor.FieldDescriptor(
-      name='stderr_log_level', full_name='apphosting.tools.devappserver2.Config.stderr_log_level', index=14,
+      name='stderr_log_level', full_name='apphosting.tools.devappserver2.Config.stderr_log_level', index=15,
       number=15, type=3, cpp_type=2, label=1,
       has_default_value=True, default_value=1,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
     _descriptor.FieldDescriptor(
-      name='auth_domain', full_name='apphosting.tools.devappserver2.Config.auth_domain', index=15,
+      name='auth_domain', full_name='apphosting.tools.devappserver2.Config.auth_domain', index=16,
       number=16, type=9, cpp_type=9, label=2,
       has_default_value=False, default_value=unicode("", "utf-8"),
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
+    _descriptor.FieldDescriptor(
+      name='max_instances', full_name='apphosting.tools.devappserver2.Config.max_instances', index=17,
+      number=18, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
   ],
   extensions=[
   ],
@@ -163,7 +177,7 @@
   is_extendable=False,
   extension_ranges=[],
   serialized_start=88,
-  serialized_end=662,
+  serialized_end=714,
 )
 
 
@@ -197,8 +211,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=664,
-  serialized_end=729,
+  serialized_start=716,
+  serialized_end=781,
 )
 
 
@@ -232,8 +246,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=731,
-  serialized_end=791,
+  serialized_start=783,
+  serialized_end=843,
 )
 
 
@@ -288,8 +302,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=793,
-  serialized_end=909,
+  serialized_start=845,
+  serialized_end=961,
 )
 
 
@@ -323,8 +337,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=911,
-  serialized_end=951,
+  serialized_start=963,
+  serialized_end=1003,
 )
 
 
@@ -358,8 +372,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=953,
-  serialized_end=990,
+  serialized_start=1005,
+  serialized_end=1042,
 )
 
 _CONFIG.fields_by_name['libraries'].message_type = _LIBRARY
diff --git a/google/appengine/tools/devappserver2/wsgi_server.py b/google/appengine/tools/devappserver2/wsgi_server.py
index c1729fc..bd64ad5 100644
--- a/google/appengine/tools/devappserver2/wsgi_server.py
+++ b/google/appengine/tools/devappserver2/wsgi_server.py
@@ -376,6 +376,11 @@
       server.quit()
 
   @property
+  def host(self):
+    """Returns the host that the server is bound to."""
+    return self._servers[0].socket.getsockname()[0]
+
+  @property
   def port(self):
     """Returns the port that the server is bound to."""
     return self._servers[0].socket.getsockname()[1]
diff --git a/google/appengine/tools/endpointscfg.py b/google/appengine/tools/endpointscfg.py
index d67da86..3a00283 100644
--- a/google/appengine/tools/endpointscfg.py
+++ b/google/appengine/tools/endpointscfg.py
@@ -58,9 +58,11 @@
 import urllib
 import urllib2
 
+from endpoints import api_config
 from protorpc import remote
 
-from google.appengine.ext.endpoints import api_config
+from google.appengine.tools.devappserver2 import api_server
+
 
 
 DISCOVERY_DOC_BASE = ('https://webapis-discovery.appspot.com/_ah/api/'
@@ -200,7 +202,7 @@
   return output_files
 
 
-def GenClientLib(discovery_path, language, output_path):
+def GenClientLib(discovery_path, language, output_path, build_system):
   """Write a client library from a discovery doc, using a cloud service to file.
 
   Args:
@@ -208,6 +210,7 @@
       library.
     language: The client library language to generate. (java)
     output_path: The directory to output the client library zip to.
+    build_system: The target build system for the client library language.
 
   Raises:
     IOError: If reading the discovery doc fails.
@@ -222,11 +225,12 @@
   client_name = re.sub(r'\.discovery$', '.zip',
                        os.path.basename(discovery_path))
 
-  _GenClientLibFromContents(discovery_doc, language, output_path, client_name)
+  _GenClientLibFromContents(discovery_doc, language, output_path, build_system,
+                            client_name)
 
 
 def _GenClientLibFromContents(discovery_doc, language, output_path,
-                              client_name):
+                              build_system, client_name):
   """Write a client library from a discovery doc, using a cloud service to file.
 
   Args:
@@ -234,6 +238,7 @@
       generate the client library.
     language: A string, the client library language to generate. (java)
     output_path: A string, the directory to output the client library zip to.
+    build_system: A string, the target build system for the client language.
     client_name: A string, the filename used to save the client lib.
 
   Raises:
@@ -244,7 +249,8 @@
     The path to the zipped client library.
   """
 
-  body = urllib.urlencode({'lang': language, 'content': discovery_doc})
+  body = urllib.urlencode({'lang': language, 'content': discovery_doc,
+                           'layout': build_system})
   request = urllib2.Request(CLIENT_LIBRARY_BASE, body)
   try:
     with contextlib.closing(urllib2.urlopen(request)) as response:
@@ -255,7 +261,7 @@
 
 
 def GetClientLib(service_class_names, doc_format, language,
-                 output_path, hostname=None):
+                 output_path, build_system, hostname=None):
   """Fetch discovery documents and client libraries from a cloud service.
 
   Args:
@@ -263,6 +269,7 @@
     doc_format: The requested format for the discovery doc. (rest|rpc)
     language: The client library language to generate. (java)
     output_path: The directory to output the discovery docs to.
+    build_system: The target build system for the client library language.
     hostname: A string hostname which will be used as the default version
       hostname. If no hostname is specificied in the @endpoints.api decorator,
       this value is the fallback. Defaults to None.
@@ -277,7 +284,7 @@
   client_libs = []
   for discovery_path in discovery_files:
     client_libs.append(
-        GenClientLib(discovery_path, language, output_path))
+        GenClientLib(discovery_path, language, output_path, build_system))
   return discovery_files, client_libs
 
 
@@ -306,12 +313,18 @@
     args: An argparse.Namespace object to extract parameters from.
     client_func: A function that generates client libraries and stores them to
       files, accepting a list of service names, a discovery doc format, a client
-      library language, and an output directory.
+      library language, an output directory, a build system for the client
+      library language, and a hostname.
   """
-  service_class_names, doc_format, language, output_path, hostname = (
-      args.service, args.format, args.language, args.output, args.hostname)
+  service_class_names = args.service
+  doc_format = args.format
+  language = args.language
+  output_path = args.output
+  hostname = args.hostname
+  build_system = args.build_system
   discovery_paths, client_paths = client_func(
-      service_class_names, doc_format, language, output_path, hostname=hostname)
+      service_class_names, doc_format, language, output_path, build_system,
+      hostname=hostname)
 
   for discovery_path in discovery_paths:
     print 'API discovery document written to %s' % discovery_path
@@ -343,12 +356,12 @@
   Args:
     args: An argparse.Namespace object to extract parameters from
     client_func: A function that generates client libraries and stores them to
-      files, accepting a path to a discovery doc, a client library language, and
-      an output directory.
+      files, accepting a path to a discovery doc, a client library language, an
+      output directory, and a build system for the client library language.
   """
-  discovery_path, language, output_path = (args.discovery_doc[0], args.language,
-                                           args.output)
-  client_path = client_func(discovery_path, language, output_path)
+  discovery_path, language, output_path, build_system = (
+      args.discovery_doc[0], args.language, args.output, args.build_system)
+  client_path = client_func(discovery_path, language, output_path, build_system)
   print 'API client library written to %s' % client_path
 
 
@@ -397,40 +410,51 @@
     if 'discovery_doc' in args:
       parser.add_argument('discovery_doc', nargs=1,
                           help='Path to the discovery document')
+    if 'build_system' in args:
+      parser.add_argument('-bs', '--build_system', default='default',
+                          help='The target build system')
 
   parser = argparse.ArgumentParser(prog=prog)
-  subparsers = parser.add_subparsers(title='subcommands')
+  subparsers = parser.add_subparsers(
+      title='subcommands', metavar='{get_client_lib, get_discovery_doc}')
 
   get_client_lib = subparsers.add_parser(
       'get_client_lib', help=('Generates discovery documents and client '
                               'libraries from service classes'))
   get_client_lib.set_defaults(callback=_GetClientLibCallback)
   AddStandardOptions(get_client_lib, 'application', 'format', 'hostname',
-                     'output', 'language', 'service')
+                     'output', 'language', 'service', 'build_system')
 
-  gen_api_config = subparsers.add_parser(
-      'gen_api_config', help=('Generates an .api file for the given service '
-                              'classes'))
+  get_discovery_doc = subparsers.add_parser(
+      'get_discovery_doc',
+      help='Generates discovery documents from service classes')
+  get_discovery_doc.set_defaults(callback=_GenDiscoveryDocCallback)
+  AddStandardOptions(get_discovery_doc, 'application', 'format', 'hostname',
+                     'output', 'service')
+
+
+
+  gen_api_config = subparsers.add_parser('gen_api_config')
   gen_api_config.set_defaults(callback=_GenApiConfigCallback)
   AddStandardOptions(gen_api_config, 'application', 'hostname', 'output',
                      'service')
 
-  gen_discovery_doc = subparsers.add_parser(
-      'gen_discovery_doc',
-      help='Generates discovery documents from service classes')
+  gen_discovery_doc = subparsers.add_parser('gen_discovery_doc')
   gen_discovery_doc.set_defaults(callback=_GenDiscoveryDocCallback)
   AddStandardOptions(gen_discovery_doc, 'application', 'format', 'hostname',
                      'output', 'service')
 
-  gen_client_lib = subparsers.add_parser(
-      'gen_client_lib', help='Generates a client library from service classes')
+  gen_client_lib = subparsers.add_parser('gen_client_lib')
   gen_client_lib.set_defaults(callback=_GenClientLibCallback)
-  AddStandardOptions(gen_client_lib, 'output', 'language', 'discovery_doc')
+  AddStandardOptions(gen_client_lib, 'output', 'language', 'discovery_doc',
+                     'build_system')
 
   return parser
 
 
 def main(argv):
+  api_server.test_setup_stubs(app_id='_')
+
   parser = MakeParser(argv[0])
   args = parser.parse_args(argv[1:])
 
diff --git a/google/appengine/tools/handler.py b/google/appengine/tools/handler.py
new file mode 100644
index 0000000..478dccb
--- /dev/null
+++ b/google/appengine/tools/handler.py
@@ -0,0 +1,531 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Code for representing and manipulating handlers in app.yaml.
+
+App.yaml requires developers to list handlers - specifications for how URL
+requests in their app are handled. This module contains classes for
+representing handlers, as well as functions for creating handlers from
+specifications in appengine-web.xml and web.xml, manipulating and matching
+paths, and ordering them correctly so that the yaml file preserves the semantics
+of the user-specified xml.
+
+In this module:
+  Handler: Ancestor class that provides pattern matching and other utilities.
+  SimpleHandler: A representation of path handling specified in XML - a path
+    along with properties detailing how it is handled.
+  OverlappedHandler: A representation of combinations of paths specified by
+    users in Xml. OverlappedHandlers combine properties, such as security
+    settings, from the SimpleHandlers that they are made up of.
+  GetOrderedIntersection: Returns an ordered list of Handlers that can be
+    written directly to Yaml.
+"""
+
+import fnmatch
+import itertools
+import re
+
+
+class Handler(object):
+  """Ancestor class for Handler manipulation. Patterns are globs.
+
+  (http://en.wikipedia.org/wiki/Glob_(programming)).
+  """
+
+  ALL_PROPERTIES = [
+      'expiration',
+      'http_headers',
+      'required_role',
+      'transport_guarantee',
+      'type',
+      'welcome'
+      ]
+
+  def __init__(self, pattern):
+    self.pattern = pattern
+
+  def _GetPattern(self):
+    return self._pattern
+
+  def _SetPattern(self, the_pattern):
+    self._pattern = the_pattern
+    self._regex = re.compile(re.escape(the_pattern).replace('\\*', '.*') + '$')
+    self.is_literal = '*' not in the_pattern
+
+  pattern = property(_GetPattern, _SetPattern)
+
+  @property
+  def regex(self):
+    return self._regex
+
+  def Regexify(self):
+    """Returns a regex-looking string to write to Yaml."""
+
+    return self.pattern.replace('.', '\\.').replace('*', '.*')
+
+  def MatchesString(self, pattern_str):
+    """Returns true if input path string is matched by glob pattern."""
+    return self._regex.match(pattern_str) is not None
+
+  def MatchesAll(self, other_glob):
+    """Returns True if self matches everything other_glob matches."""
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+    return self.MatchesString(other_glob.pattern)
+
+  def HasMoreSpecificPatternThan(self, other_handler):
+    """Returns True if self is more specific than other_handler.
+
+    Priority in determining specificity is first determined by literal-ness,
+    second by length. This is according to the Java servlet spec for
+    mapping URL paths.
+
+    Args:
+      other_handler: another handler to compare against.
+
+    Returns:
+      True if self.pattern is a literal and other_handler.pattern is not,
+      False if vice versa, and otherwise True if self.pattern is longer.
+    """
+
+    if self.is_literal != other_handler.is_literal:
+      return self.is_literal
+
+    return len(self.pattern) > len(other_handler.pattern)
+
+  def __eq__(self, other_handler):
+    return (isinstance(other_handler, Handler) and
+            self.__dict__ == other_handler.__dict__)
+
+  def IsFullyHandledBy(self, other_handler):
+    """Returns True if self specifies something unique.
+
+    For example, If we have a Handler with pattern "foo*bar"
+    which has properties {'type': 'static'}, and other_handler
+    has pattern "foo*" with the same properties, then
+    other_handler does everything that self does.
+
+    Args:
+      other_handler: other handler to be matched against
+    Returns:
+      Boolean value of whether other_handler fully handles self.
+    """
+    return (other_handler.MatchesAll(self) and
+            self._PropertiesMatch(other_handler))
+
+  def _PropertiesMatch(self, other):
+    """Returns True if other.properties is superset of self.properties."""
+    for prop in Handler.ALL_PROPERTIES:
+      if self.GetProperty(prop) not in (None, other.GetProperty(prop)):
+        return False
+    return True
+
+
+def _MakeHandlerList(*pattern_strings):
+  return [SimpleHandler(a) for a in pattern_strings]
+
+
+class SimpleHandler(Handler):
+  """Subclass of Handler which includes user-defined settings with urls.
+
+  SimpleHandlers should be treated as immutable.
+  """
+
+  def __init__(self, pattern, properties=None):
+    super(SimpleHandler, self).__init__(pattern)
+    if properties:
+      self.properties = properties
+    else:
+      self.properties = {}
+
+  def __hash__(self):
+    return hash((self.pattern, tuple(sorted(self.properties.items()))))
+
+  def GetProperty(self, prop, default=None):
+    return self.properties.get(prop, default)
+
+  def CreateOverlappedHandler(self):
+    """Creates a Combined Handler with self's pattern and self as a child."""
+    return OverlappedHandler(self.pattern, matchers=[self])
+
+
+class OverlappedHandler(Handler):
+  """Subclass of Handler which allows for the combination of SimpleHandlers.
+
+  An intuitive way to think about globbed patterns is as sets - for example, the
+  pattern "admin/*" is the set of all paths that are in the admin/ directory,
+  and the pattern "*.txt" is the set of all paths to text files. An
+  OverlappedHandler is designed to describe the intersection of the sets
+  of paths - ie the set of paths that is matched by EVERY one its
+  handler patterns.
+
+  In the Xml files, paths are specified in different places - in servlet
+  patterns, in static files includes, in security constraint specifications,
+  etc. There is often some overlap between the paths that are specified by
+  these patterns. Since App.Yaml does not have separate places to specify how
+  different paths are handled, but rather just lists paths along with a bunch
+  of ways that the paths is handled, we need to make sure that these properties
+  for various paths are being combined at some point in the translation process.
+
+  Thus an OverlappedHandler holds a list of "matchers" - ie. handlers with more
+  general patterns, to choose properties from. OverlappedHandlers do not
+  explicitly specify properties but rather choose the value from the most
+  specific "matcher" with the value of that property specified. The matchers
+  are SimpleHandlers.
+
+  Attributes:
+    pattern: inherited from Handler.
+    matchers: A list of SimpleHandlers. Matchers are handlers which happen to
+      match OverlappedHandler's pattern and whose properties are thus necessary
+      to track in order to make sure that OverlappedHandler's pattern is handled
+      correctly.
+  """
+
+  def __init__(self, pattern, matchers=()):
+    super(OverlappedHandler, self).__init__(pattern)
+    self.matchers = []
+    for sub_handler in matchers:
+      self.AddMatchingHandler(sub_handler)
+
+  def GetProperty(self, prop, default=None):
+    """Returns the property value of matcher with most specific pattern."""
+
+    largest_handler = None
+    prop_value = default
+    for sub_handler in self.matchers:
+      if sub_handler.GetProperty(prop) is not None:
+        if (not largest_handler or
+            sub_handler.HasMoreSpecificPatternThan(largest_handler)):
+          largest_handler = sub_handler
+          prop_value = sub_handler.GetProperty(prop)
+    return prop_value
+
+  def __eq__(self, other_handler):
+    return (isinstance(other_handler, OverlappedHandler) and
+            self.pattern == other_handler.pattern and
+            set(self.matchers) == set(other_handler.matchers))
+
+  def AddMatchingHandler(self, matcher):
+    """Flattens the handler if it is overlapped and adds to matchers."""
+    if isinstance(matcher, SimpleHandler):
+      self.matchers.append(matcher)
+    else:
+      self.matchers.extend(matcher.matchers)
+
+
+def GetOrderedIntersection(handler_list):
+  """Implements algorithm for combining and reordering Handlers.
+
+  GetOrderedIntersection performs the heavy lifting of converting a randomly
+  ordered list of Handlers (globbed patterns, each with various potentially
+  conflicting properties attached), into an ordered list of handlers with
+  property values resolved.
+
+  The purpose of this process is to convert the Web.xml URL mapping scheme to
+  that of app.yaml. In Web.xml the most specific path, according to
+  literal-ness and length, is chosen. In app.yaml the first listed matching
+  path is chosen. Thus to preserve user preferences through the translation
+  process we order the patterns from specific to general.
+
+  For example, if three handlers are given as input (in any order):
+
+  "/admin/*" (security=admin)
+  "/*.png" (type=static)
+  "*" (type=dynamic, security=required)
+
+  we want to get this ordered list as output:
+  1. "/admin/*.png" (security=admin, type=static)
+  2. "/admin/*" (security=admin, type=dynamic)
+  3. "/*.png" (security=required, type=static)
+  4. "*" (type=dynamic, security=required).
+
+  so that the properties of any given path are those of the longest matching
+  path. The SimpleHandler and OverlappedHandler classes provide the logic for
+  attaching globbed patterns to the right properties and resolving potential
+  property value conflicts.
+
+  Args:
+    handler_list: List of SimpleHandlers in arbitrary order.
+  Returns:
+    An ordered list of OverlappedHandlers and SimpleHandlers. See the above
+    example for what this would look like.
+  """
+  results = _Intersect(handler_list)
+
+
+
+
+  results = sorted(results, key=lambda h: h.pattern)
+  _ReorderHandlers(results)
+  _GivePropertiesFromGeneralToSpecific(results)
+  return _RemoveRedundantHandlers(results)
+
+
+def _RemoveRedundantHandlers(handler_list):
+  """Removes duplicated or unnecessary handlers from the list.
+
+  If a handler's pattern and functionality are fully matched by another handler
+  with a more general pattern, we do not need the  first handler. At the same
+  time, we remove duplicates.
+
+  Args:
+    handler_list: list of ordered handlers with possibly redundant entries.
+  Returns:
+    new list which contains entries of handler_list, except redundant ones.
+  """
+
+  no_duplicates = []
+  patterns_found_so_far = set()
+  for i in xrange(len(handler_list)):
+    current_handler = handler_list[i]
+    matched_by_later = False
+    for j in xrange(i + 1, len(handler_list)):
+      if current_handler.IsFullyHandledBy(handler_list[j]):
+        matched_by_later = True
+        break
+
+    if (not matched_by_later and
+        current_handler.pattern not in patterns_found_so_far):
+      no_duplicates.append(current_handler)
+      patterns_found_so_far.add(current_handler.pattern)
+
+  return no_duplicates
+
+
+def _ReorderHandlers(handler_list):
+  """Reorders handlers from specific to general for writing to yaml file.
+
+  This is a topological sort - ie. it makes sure that elements related to
+  each other are ordered correctly. In this case, we want to make sure that
+  any Handler with a pattern that matches all of the Handlers of another pattern
+  occurs later. Thus, we want to make sure that "foo*" occurs after "foo*bar",
+  but it does not matter how it is ordered relative to "*baz", since they have
+  an empty intersection of patterns.
+
+  The problem with using Python's built-in sorted is that it relies on the
+  class's less-than operator. We want an ordering such that
+    (handler1 < handler2) iff (not handler1.MatchesAll(handler2))
+  Then, since "foo*" and "*baz" do not contain each other, foo* < *baz == True
+  and *baz < foo* == True. This is a problem because Python's sorted does not
+  explicitly compare every pair of elements, but operates under the assumption
+  that if a < b and b < c, then a < c. Therefore, if we have
+    a = "foo*bar", b = "*baz", c = "foo*"
+  then a < b == True and b < c == True, so a < c is assumed to be True. This
+  often leads to wrong orderings.
+
+  Therefore, this function performs a topological sort
+  (http://en.wikipedia.org/wiki/Topological_sorting), reordering only those
+  patterns where one matches all of the other.
+
+  This is an in-place sort.
+
+  Args:
+    handler_list: Unordered list of handlers.
+  """
+  for i, j in itertools.combinations(xrange(len(handler_list)), 2):
+    if handler_list[i].MatchesAll(handler_list[j]):
+      handler_list[i], handler_list[j] = handler_list[j], handler_list[i]
+
+
+def _GivePropertiesFromGeneralToSpecific(handler_list):
+  """Makes sure that handlers have all properties of more general ones.
+
+  Ex. Since "*" matches everything "admin/*" matches, we want everything
+  matched by "admin/*" to have all the properties specified to "*".
+  Therefore we give properties from the "*" handler to the "admin/*" handler.
+  If the "*" handler is a SimpleHandler, it carries its own properties, so it
+  becomes a child of the "admin/*" handler. Otherwise, its properties are
+  define by its children, so its children are copied to the "admin/*"
+  handler.
+
+  This is an in-place mutation of the list.
+
+  Args:
+    handler_list: List of ordered Handlers.
+  """
+  for i, j in itertools.combinations(xrange(len(handler_list)), 2):
+    if handler_list[j].MatchesAll(handler_list[i]):
+      if isinstance(handler_list[i], SimpleHandler):
+        handler_list[i] = handler_list[i].CreateOverlappedHandler()
+      handler_list[i].AddMatchingHandler(handler_list[j])
+
+
+def _Intersect(handler_list):
+  """Returns an unordered list of all possible intersections of handlers."""
+  if not handler_list:
+    return set()
+
+  handlers = set([handler_list[0]])
+
+
+
+
+  for input_handler in handler_list[1:]:
+    new_handlers = set()
+    for g in handlers:
+      new_handlers |= _IntersectTwoHandlers(input_handler, g)
+    handlers = new_handlers
+  return list(handlers)
+
+
+def _IntersectTwoHandlers(first_handler, second_handler):
+  """Returns intersections of first_handler and second_handler patterns."""
+  shared_prefix = _SharedPrefix(first_handler.pattern, second_handler.pattern)
+
+  if shared_prefix:
+    return _HandleCommonPrefix(first_handler, second_handler, shared_prefix)
+
+
+  shared_suffix = _SharedSuffix(first_handler.pattern, second_handler.pattern)
+  if shared_suffix:
+    return _HandleCommonSuffix(first_handler, second_handler, shared_suffix)
+
+  handler_set = set()
+  handler_set |= _HandleWildcardCases(first_handler, second_handler)
+
+
+  handler_set |= _HandleWildcardCases(second_handler, first_handler)
+
+  handler_set |= set([first_handler, second_handler])
+
+  return handler_set
+
+
+def _HandleWildcardCases(first_handler, second_handler):
+  """Handle cases with trailing and leading wildcards.
+
+  This function finds the set of intersections of two handlers where one has a
+  leading wildcard (eg. *foo) in its pattern and at least one has a trailing
+  wildcard (eg. baz*) in its pattern. The arguments are not symmetric.
+
+  Args:
+    first_handler: A SimpleHandler instance
+    second_handler: A SimpleHandler instance
+  Returns:
+    A set of intersection patterns of the two Handlers. Example: If the
+    pattern of first_handler is abc* and that of the second is *xyz, we return
+    the intersection of the patterns, abc*xyz. Find more examples in the inline
+    comments.
+  """
+  merged_handlers = set()
+
+  if len(first_handler.pattern) <= 1 or len(second_handler.pattern) <= 1:
+    return merged_handlers
+
+  if (first_handler.pattern[-1], second_handler.pattern[0]) != ('*', '*'):
+    return merged_handlers
+
+
+
+  first_no_star = first_handler.pattern[:-1]
+  merged_handlers.add(SimpleHandler(first_no_star + second_handler.pattern))
+  if second_handler.MatchesString(first_no_star):
+
+
+    merged_handlers.add(SimpleHandler(first_no_star))
+  return merged_handlers
+
+
+def _HandleCommonPrefix(first_handler, second_handler, common_prefix):
+  """Strips common literal prefix from handlers and intersects the substrings.
+
+  Ex. "abc" and "a*c" become "a | bc" and "a | *c". We find the set of
+  intersections of "bc" and "*c" and prepend "a" onto each member of that set.
+  By common literal prefix, we mean a prefix of the two patterns that contains
+  no wildcard characters; any string matched by either of the patterns must
+  begin with that prefix.
+
+  Args:
+    first_handler: A SimpleHandler.
+    second_handler: A SimpleHandler.
+    common_prefix: The shared literal prefix of the patterns of the two
+    handlers.
+  Returns:
+    The set of intersections of first_handler and second_handler. This is done
+    by stripping the common prefix to create new SimpleHandlers which we call
+    _IntersectTwoHandlers on, and then prepend the prefix to each member of that
+    set.
+  """
+  stripped_first_handler = SimpleHandler(
+      first_handler.pattern[len(common_prefix):], first_handler.properties)
+  stripped_second_handler = SimpleHandler(
+      second_handler.pattern[len(common_prefix):], second_handler.properties)
+  stripped_handlers = _IntersectTwoHandlers(stripped_first_handler,
+                                            stripped_second_handler)
+  handlers = set()
+  for stripped_handler in stripped_handlers:
+    handlers.add(SimpleHandler(common_prefix + stripped_handler.pattern,
+                               stripped_handler.properties))
+  return handlers
+
+
+def _HandleCommonSuffix(first_handler, second_handler, common_suffix):
+  """Strips matching suffix from handlers and intersects the substrings."""
+
+  stripped_first_handler = SimpleHandler(
+      first_handler.pattern[:-len(common_suffix)], first_handler.properties)
+  stripped_second_handler = SimpleHandler(
+      second_handler.pattern[:-len(common_suffix)], second_handler.properties)
+  stripped_handlers = _IntersectTwoHandlers(
+      stripped_first_handler, stripped_second_handler)
+  handlers = set()
+  for stripped_handler in stripped_handlers:
+    handlers.add(SimpleHandler(stripped_handler.pattern + common_suffix,
+                               stripped_handler.properties))
+  return handlers
+
+
+def _SharedPrefix(pattern1, pattern2):
+  """Returns the shared prefix of two patterns.
+
+  Args:
+    pattern1: A handler's pattern string
+    pattern2: A handler's pattern string
+  Returns:
+    The shared prefix of the two patterns, up to the index of the first
+    wildcard of either pattern. For example, the shared prefix of "a*bd" and
+    "ac*d" is a. The shared prefix of "*x" and "y" is the empty string. The
+    shared prefix of "john" and "johnny" is the empty string, and the shared
+    prefix of "bc*" and "c*" is also the empty string.
+  """
+  first_star1 = (pattern1 + '*').find('*')
+  first_star2 = (pattern2 + '*').find('*')
+  if (first_star1, first_star2) != (len(pattern1), len(pattern2)):
+    min_star = min(first_star1, first_star2)
+    if min_star and pattern1[:min_star] == pattern2[:min_star]:
+      return pattern1[:min_star]
+  return ''
+
+
+def _SharedSuffix(pattern1, pattern2):
+  """Returns the shared suffix of two patterns."""
+  return _SharedPrefix(pattern1[::-1], pattern2[::-1])[::-1]
diff --git a/google/appengine/tools/handler_generator.py b/google/appengine/tools/handler_generator.py
new file mode 100644
index 0000000..97a1535
--- /dev/null
+++ b/google/appengine/tools/handler_generator.py
@@ -0,0 +1,307 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Contains logic for writing handlers to app.yaml.
+
+Information about handlers comes from static file paths, appengine-web.xml,
+and web.xml. This information is packaged into Handler objects which specify
+paths and properties about how they are handled.
+
+In this module:
+  GenerateYamlHandlers: Returns Yaml string with handlers.
+  HandlerGenerator: Ancestor class for creating handler lists.
+  DynamicHandlerGenerator: Creates handlers from web-xml servlet and filter
+    mappings.
+  StaticHandlerGenerator: Creates handlers from static file includes and
+    static files.
+"""
+
+from google.appengine.tools import handler
+from google.appengine.tools.app_engine_config_exception import AppEngineConfigException
+
+API_ENDPOINT_REGEX = '/_ah/spi/*'
+MAX_HANDLERS = 100
+
+
+def GenerateYamlHandlersList(app_engine_web_xml, web_xml, static_files):
+  """Produces a list of Yaml strings for dynamic and static handlers."""
+  static_handler_generator = StaticHandlerGenerator(
+      app_engine_web_xml, web_xml, static_files)
+  dynamic_handler_generator = DynamicHandlerGenerator(
+      app_engine_web_xml, web_xml)
+
+  if (len(static_handler_generator.GenerateOrderedHandlerList()) +
+      len(dynamic_handler_generator.GenerateOrderedHandlerList())
+      > MAX_HANDLERS):
+
+
+
+
+    dynamic_handler_generator.fall_through = True
+    dynamic_handler_generator.welcome_properties = {}
+
+  yaml_statements = ['handlers:']
+  if static_files:
+    static_handler_generator = StaticHandlerGenerator(
+        app_engine_web_xml, web_xml, static_files)
+    yaml_statements += static_handler_generator.GetHandlerYaml()
+  yaml_statements += dynamic_handler_generator.GetHandlerYaml()
+
+  return yaml_statements
+
+
+def GenerateYamlHandlers(app_engine_web_xml, web_xml, static_files):
+  """Produces Yaml string writable to a file."""
+  handler_yaml = '\n'.join(
+      GenerateYamlHandlersList(app_engine_web_xml, web_xml, static_files))
+  return handler_yaml + '\n'
+
+
+class HandlerGenerator(object):
+  """Ancestor class for StaticHandlerGenerator and DynamicHandlerGenerator.
+
+  Contains shared functionality. Both static and dynamic handler generators
+  work in a similar way. Both obtain a list of patterns, static includes and
+  web.xml servlet url patterns, respectively, add security constraint info to
+  those lists, sort them, and then generate Yaml statements for each entry.
+  """
+
+  def __init__(self, app_engine_web_xml, web_xml):
+    self.app_engine_web_xml = app_engine_web_xml
+    self.web_xml = web_xml
+
+  def GetHandlerYaml(self):
+    handler_yaml = []
+    for h in self.GenerateOrderedHandlerList():
+      handler_yaml += self.TranslateHandler(h)
+    return handler_yaml
+
+  def GenerateSecurityConstraintHandlers(self):
+    """Creates Handlers for security constraint information."""
+    handler_list = []
+    for constraint in self.web_xml.security_constraints:
+      props = {'transport_guarantee': constraint.transport_guarantee,
+               'required_role': constraint.required_role}
+      for pattern in constraint.patterns:
+        security_handler = handler.SimpleHandler(pattern, props)
+        handler_list.append(security_handler)
+        handler_list += self.CreateHandlerWithoutTrailingStar(security_handler)
+    return handler_list
+
+  def GenerateWelcomeFileHandlers(self):
+    """Creates handlers for welcome files."""
+    if not self.welcome_properties:
+      return []
+
+
+
+    return [handler.SimpleHandler('/', self.welcome_properties),
+            handler.SimpleHandler('/*/', self.welcome_properties)]
+
+  def TranslateAdditionalOptions(self, h):
+    """Generates Yaml statements from security constraint information."""
+    additional_statements = []
+
+    required_role = h.GetProperty('required_role', default='none')
+    required_role_translation = {'none': 'optional',
+                                 '*': 'required',
+                                 'admin': 'admin'}
+    additional_statements.append(
+        '  login: %s' % required_role_translation[required_role])
+
+    transport_guarantee = h.GetProperty('transport_guarantee', default='none')
+
+    if transport_guarantee == 'none':
+      if self.app_engine_web_xml.ssl_enabled:
+        additional_statements.append('  secure: optional')
+      else:
+        additional_statements.append('  secure: never')
+    else:
+      if self.app_engine_web_xml.ssl_enabled:
+        additional_statements.append('  secure: always')
+      else:
+        raise AppEngineConfigException(
+            'SSL must be enabled in appengine-web.xml to use '
+            'transport-guarantee')
+
+    handler_id = self.web_xml.pattern_to_id.get(h.pattern)
+    if handler_id and handler_id in self.app_engine_web_xml.api_endpoints:
+      additional_statements.append('  api_endpoint: True')
+    return additional_statements
+
+  def CreateHandlerWithoutTrailingStar(self, h):
+    """Creates identical handler without trailing star in pattern.
+
+    According to servlet spec, baz/* should match baz.
+
+    Args:
+      h: a Handler.
+    Returns:
+      If h.pattern is of form "baz/*", returns a singleton list with a
+      SimpleHandler with pattern "baz" and the properties of h. Otherwise,
+      returns an empty list.
+    """
+    if len(h.pattern) <= 2 or h.pattern[-2:] != '/*':
+      return []
+    return [handler.SimpleHandler(h.pattern[:-2], h.properties)]
+
+
+class DynamicHandlerGenerator(HandlerGenerator):
+  """Generates dynamic handler yaml entries for app.yaml."""
+
+  def __init__(self, app_engine_web_xml, web_xml):
+    super(DynamicHandlerGenerator, self).__init__(app_engine_web_xml, web_xml)
+    if any([self.web_xml.fall_through_to_runtime,
+            '/' in self.web_xml.patterns,
+            '/*' in self.web_xml.patterns]):
+      self.fall_through = True
+      self.welcome_properties = {}
+    else:
+      self.fall_through = False
+      self.welcome_properties = {'type': 'dynamic'}
+
+    self.has_api_endpoint = API_ENDPOINT_REGEX in self.web_xml.patterns
+    self.patterns = [pattern for pattern in self.web_xml.patterns if
+                     pattern not in ('/', '/*', API_ENDPOINT_REGEX)]
+
+  def MakeServletPatternsIntoHandlers(self):
+    """Creates SimpleHandlers from servlet and filter mappings in web.xml."""
+    handler_patterns = []
+    has_jsps = False
+    if self.fall_through:
+      return [handler.SimpleHandler('/*', {'type': 'dynamic'})]
+
+    for pattern in self.patterns:
+      if pattern.endswith('.jsp'):
+        has_jsps = True
+      else:
+        new_handler = handler.SimpleHandler(pattern, {'type': 'dynamic'})
+        handler_patterns.append(new_handler)
+        handler_patterns += self.CreateHandlerWithoutTrailingStar(new_handler)
+
+    if has_jsps or self.app_engine_web_xml.use_vm:
+      handler_patterns.append(
+          handler.SimpleHandler('*.jsp', {'type': 'dynamic'}))
+
+    handler_patterns.append(
+        handler.SimpleHandler('/_ah/*', {'type': 'dynamic'}))
+    return handler_patterns
+
+  def GenerateOrderedHandlerList(self):
+    handler_patterns = (self.MakeServletPatternsIntoHandlers()
+                        + self.GenerateSecurityConstraintHandlers()
+                        + self.GenerateWelcomeFileHandlers())
+    ordered_handlers = handler.GetOrderedIntersection(handler_patterns)
+    if self.has_api_endpoint:
+      ordered_handlers.append(
+          handler.SimpleHandler(API_ENDPOINT_REGEX, {'type': 'dynamic'}))
+    return ordered_handlers
+
+  def TranslateHandler(self, the_handler):
+    """Converts a dynamic handler object to Yaml."""
+    if the_handler.GetProperty('type') != 'dynamic':
+      return []
+    statements = ['- url: %s' % the_handler.Regexify(),
+                  '  script: unused']
+    return statements + self.TranslateAdditionalOptions(the_handler)
+
+
+class StaticHandlerGenerator(HandlerGenerator):
+  """Generates static handler yaml entries for app.yaml."""
+
+  def __init__(self, app_engine_web_xml, web_xml, static_files):
+    super(StaticHandlerGenerator, self).__init__(app_engine_web_xml, web_xml)
+    self.static_files = static_files
+    static_welcome_files = []
+    for welcome_file in self.web_xml.welcome_files:
+      for static_file in static_files:
+        if static_file.endswith('/' + welcome_file):
+          static_welcome_files.append(welcome_file)
+          break
+
+    welcome_value = tuple(static_welcome_files) or None
+    self.welcome_properties = {'welcome': welcome_value}
+
+  def MakeStaticFilePatternsIntoHandlers(self):
+    """Creates SimpleHandlers out of XML-specified static file includes."""
+    includes = self.app_engine_web_xml.static_file_includes
+    if not includes:
+      return [handler.SimpleHandler('/*', {'type': 'static'})]
+
+    handler_patterns = []
+
+    for include in includes:
+      pattern = include.pattern.replace('**', '*')
+      if pattern[0] != '/':
+        pattern = '/' + pattern
+      properties = {'type': 'static'}
+      if include.expiration:
+        properties['expiration'] = include.expiration
+      if include.http_headers:
+
+        properties['http_headers'] = tuple(sorted(include.http_headers.items()))
+      handler_patterns.append(handler.SimpleHandler(pattern, properties))
+    return handler_patterns
+
+  def GenerateOrderedHandlerList(self):
+    handler_patterns = (self.MakeStaticFilePatternsIntoHandlers()
+                        + self.GenerateSecurityConstraintHandlers()
+                        + self.GenerateWelcomeFileHandlers())
+    return handler.GetOrderedIntersection(handler_patterns)
+
+  def TranslateHandler(self, h):
+    """Translates SimpleHandler to static handler yaml statements."""
+    root = self.app_engine_web_xml.public_root
+    regex_string = h.Regexify()
+    if regex_string.startswith(root):
+      regex_string = regex_string[len(root):]
+
+    welcome_files = h.GetProperty('welcome')
+
+    if welcome_files:
+      statements = []
+      for welcome_file in welcome_files:
+        statements += ['- url: (%s)' % regex_string,
+                       '  static_files: __static__%s\\1%s' %
+                       (root, welcome_file),
+                       '  upload: __NOT_USED__',
+                       '  require_matching_file: True']
+        statements += self.TranslateAdditionalOptions(h)
+        statements += self.TranslateAdditionalStaticOptions(h)
+      return statements
+
+    if h.GetProperty('type') != 'static':
+      return []
+
+    statements = ['- url: (%s)' % regex_string,
+                  '  static_files: __static__%s\\1' % root,
+                  '  upload: __NOT_USED__',
+                  '  require_matching_file: True']
+
+    return (statements +
+            self.TranslateAdditionalOptions(h) +
+            self.TranslateAdditionalStaticOptions(h))
+
+  def TranslateAdditionalStaticOptions(self, h):
+    statements = []
+    expiration = h.GetProperty('expiration')
+    if expiration:
+      statements.append('  expiration: %s' % expiration)
+    http_headers = h.GetProperty('http_headers')
+    if http_headers:
+      statements.append('  http_headers:')
+      statements += ['    %s: %s' % pair for pair in http_headers]
+    return statements
diff --git a/google/appengine/tools/remote_api_shell.py b/google/appengine/tools/remote_api_shell.py
index 10d9b93..f6ea7f8 100644
--- a/google/appengine/tools/remote_api_shell.py
+++ b/google/appengine/tools/remote_api_shell.py
@@ -28,6 +28,7 @@
 
 
 
+
 from google.appengine.tools import os_compat
 
 import atexit
diff --git a/google/appengine/tools/web_xml_parser.py b/google/appengine/tools/web_xml_parser.py
new file mode 100644
index 0000000..d74b05f
--- /dev/null
+++ b/google/appengine/tools/web_xml_parser.py
@@ -0,0 +1,212 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Directly processes text of web.xml.
+
+WebXmlParser is called with Xml string to produce a WebXml object containing
+the data from that string.
+
+WebXmlParser: Converts xml to AppEngineWebXml object.
+WebXml: Contains relevant information from web.xml.
+SecurityConstraint: Contains information about specified security constraints.
+
+"""
+
+from xml.etree import ElementTree
+
+from google.appengine.tools import xml_parser_utils
+from google.appengine.tools.app_engine_config_exception import AppEngineConfigException
+from google.appengine.tools.basic_equality_mixin import BasicEqualityMixin
+
+
+class WebXmlParser(object):
+  """Provides logic for walking down XML tree and pulling data."""
+
+  def ProcessXml(self, xml_str):
+    """Parses XML string and returns object representation of relevant info.
+
+    Uses ElementTree parser to return a tree representation of XML.
+    Then walks down that tree and extracts important info and adds it to the
+    object.
+
+    Args:
+      xml_str: The XML string itself.
+
+    Returns:
+      If there is well-formed but illegal XML, returns a list of
+      errors. Otherwise, returns an AppEngineWebXml object containing
+      information from XML.
+
+    Raises:
+      AppEngineConfigException: In case of malformed XML or illegal inputs.
+    """
+    try:
+      self.web_xml = WebXml()
+      self.errors = []
+      xml_root = ElementTree.fromstring(xml_str)
+      for node in xml_root.getchildren():
+        self.ProcessSecondLevelNode(node)
+
+      if self.errors:
+        raise AppEngineConfigException('\n'.join(self.errors))
+
+      return self.web_xml
+
+    except ElementTree.ParseError:
+      raise AppEngineConfigException('Bad input -- not valid XML')
+
+  def ProcessSecondLevelNode(self, node):
+    element_name = xml_parser_utils.GetTag(node)
+    if element_name in ['servlet', 'filter', 'display-name', 'taglib']:
+
+
+      return
+    camel_case_name = ''.join(part.title() for part in element_name.split('-'))
+    method_name = 'Process%sNode' % camel_case_name
+    if (hasattr(self, method_name) and
+        method_name is not 'ProcessSecondLevelNode'):
+      getattr(self, method_name)(node)
+    else:
+      self.errors.append('Second-level tag not recognized: %s' % element_name)
+
+  def ProcessServletMappingNode(self, node):
+    self._ProcessUrlMappingNode(node)
+
+  def ProcessFilterMappingNode(self, node):
+    self._ProcessUrlMappingNode(node)
+
+  def _ProcessUrlMappingNode(self, node):
+    """Parses out URL and possible ID for filter-mapping and servlet-mapping.
+
+    Pulls url-pattern text out of node and adds to WebXml object. If url-pattern
+    has an id attribute, adds that as well. This is done for <servlet-mapping>
+    and <filter-mapping> nodes.
+
+    Args:
+      node: An ElementTreeNode which looks something like the following:
+
+        <servlet-mapping>
+          <servlet-name>redteam</servlet-name>
+          <url-pattern>/red/*</url-pattern>
+        </servlet-mapping>
+    """
+    url_pattern_node = xml_parser_utils.GetChild(node, 'url-pattern')
+    if url_pattern_node is not None:
+      self.web_xml.patterns.append(url_pattern_node.text)
+      id_attr = xml_parser_utils.GetAttribute(url_pattern_node, 'id')
+      if id_attr:
+        self.web_xml.pattern_to_id[url_pattern_node.text] = id_attr
+
+  def ProcessErrorPageNode(self, node):
+    """Process error page specifications.
+
+    If one of the supplied error codes is 404, allow fall through to runtime.
+
+    Args:
+      node: An ElementTreeNode which looks something like the following.
+        <error-page>
+          <error-code>500</error-code>
+          <location>/errors/servererror.jsp</location>
+        </error-page>
+    """
+
+    error_code = xml_parser_utils.GetChildNodeText(node, 'error-code')
+    if error_code == '404':
+      self.web_xml.fall_through_to_runtime = True
+
+  def ProcessWelcomeFileListNode(self, node):
+    for welcome_node in xml_parser_utils.GetNodes(node, 'welcome-file'):
+      welcome_file = welcome_node.text
+      if welcome_file and welcome_file[0] == '/':
+        self.errors.append('Welcome files must be relative paths: %s' %
+                           welcome_file)
+        continue
+      self.web_xml.welcome_files.append(welcome_file)
+
+  def ProcessMimeMappingNode(self, node):
+    extension = xml_parser_utils.GetChildNodeText(node, 'extension')
+    mime_type = xml_parser_utils.GetChildNodeText(node, 'mime-type')
+
+    if not extension:
+      self.errors.append('<mime-type> without extension')
+      return
+    self.web_xml.mime_mappings[extension] = mime_type
+
+  def ProcessSecurityConstraintNode(self, node):
+    """Pulls data from the security constraint node and adds to WebXml object.
+
+    Args:
+      node: An ElementTree Xml node that looks something like the following:
+
+        <security-constraint>
+          <web-resource-collection>
+            <url-pattern>/profile/*</url-pattern>
+          </web-resource-collection>
+          <user-data-constraint>
+            <transport-guarantee>CONFIDENTIAL</transport-guarantee>
+          </user-data-constraint>
+        </security-constraint>
+    """
+    security_constraint = SecurityConstraint()
+    resources_node = xml_parser_utils.GetChild(node, 'web-resource-collection')
+    security_constraint.patterns = [
+        sub_node.text for sub_node in xml_parser_utils.GetNodes(
+            resources_node, 'url-pattern')]
+    constraint = xml_parser_utils.GetChild(node, 'auth-constraint')
+    if constraint is not None:
+      role_name = xml_parser_utils.GetChildNodeText(
+          constraint, 'role-name').lower()
+      if role_name not in ('none', '*', 'admin'):
+        self.errors.append('Bad value for <role-name> (%s), must be none, '
+                           '*, or admin' % role_name)
+      security_constraint.required_role = role_name
+
+    user_constraint = xml_parser_utils.GetChild(node, 'user-data-constraint')
+    if user_constraint is not None:
+      guarantee = xml_parser_utils.GetChildNodeText(
+          user_constraint, 'transport-guarantee').lower()
+      if guarantee not in ('none', 'integral', 'confidential'):
+        self.errors.append('Bad value for <transport-guarantee> (%s), must be'
+                           ' none, integral, or confidential' % guarantee)
+      security_constraint.transport_guarantee = guarantee
+
+    self.web_xml.security_constraints.append(security_constraint)
+
+
+class WebXml(BasicEqualityMixin):
+  """Contains information about web.xml relevant for translation to app.yaml."""
+
+  def __init__(self):
+    self.patterns = []
+    self.security_constraints = []
+    self.welcome_files = []
+    self.mime_mappings = {}
+    self.pattern_to_id = {}
+    self.fall_through_to_runtime = False
+
+  def GetMimeTypeForPath(self, path):
+    if '.' not in path:
+      return None
+    return self.mime_mappings.get(path.split('.')[-1], None)
+
+
+class SecurityConstraint(BasicEqualityMixin):
+  """Contains information about security constraints in web.xml."""
+
+  def __init__(self):
+    self.patterns = []
+    self.transport_guarantee = 'none'
+    self.required_role = 'none'
diff --git a/google/appengine/tools/xml_parser_utils.py b/google/appengine/tools/xml_parser_utils.py
new file mode 100644
index 0000000..88cacab
--- /dev/null
+++ b/google/appengine/tools/xml_parser_utils.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Contains some functions that come in handy with XML parsing."""
+
+
+def GetTag(node):
+  """Strips namespace prefix."""
+  return node.tag.rsplit('}', 1)[-1]
+
+
+def GetChild(node, tag):
+  """Returns first child of node with tag."""
+  for child in node.getchildren():
+    if GetTag(child) == tag:
+      return child
+
+
+def BooleanValue(node_text):
+  return node_text.lower() in ('1', 'true')
+
+
+def GetAttribute(node, attr):
+  """Wrapper function to retrieve attributes from XML nodes."""
+  return node.attrib.get(attr, '')
+
+
+def GetChildNodeText(node, child_tag, default=''):
+  """Finds child xml node with desired tag and returns its text."""
+  for child in node.getchildren():
+    if GetTag(child) == child_tag:
+
+      return child.text or default
+  return default
+
+
+def GetNodes(node, match_tag):
+  """Gets all children of a node with the desired tag."""
+  return (child for child in node.getchildren() if GetTag(child) == match_tag)
diff --git a/google/appengine/tools/yaml_translator.py b/google/appengine/tools/yaml_translator.py
new file mode 100644
index 0000000..692f00c
--- /dev/null
+++ b/google/appengine/tools/yaml_translator.py
@@ -0,0 +1,292 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Performs XML-to-YAML translation.
+
+  TranslateXmlToYaml(): performs xml-to-yaml translation with
+  string inputs and outputs
+  AppYamlTranslator: Class that facilitates xml-to-yaml translation
+"""
+
+from google.appengine.tools import app_engine_web_xml_parser as aewxp
+from google.appengine.tools import backends_xml_parser
+from google.appengine.tools import handler_generator
+from google.appengine.tools import web_xml_parser
+from google.appengine.tools.app_engine_web_xml_parser import AppEngineConfigException
+
+
+NO_API_VERSION = 'none'
+
+
+def TranslateXmlToYaml(app_engine_web_xml_str,
+                       backends_xml_str,
+                       web_xml_str,
+                       static_files,
+                       api_version):
+  """Does xml-string to yaml-string translation, given each separate file text.
+
+  Processes each xml string into an object representing the xml,
+  and passes these to the translator.
+
+  Args:
+    app_engine_web_xml_str: text from app_engine_web.xml
+    backends_xml_str: text from backends.xml
+    web_xml_str: text from web.xml
+    static_files: List of static files
+    api_version: current api version
+
+  Returns:
+    The full text of the app.yaml generated from the xml files.
+
+  Raises:
+    AppEngineConfigException: raised in processing stage for illegal XML.
+  """
+  aewx_parser = aewxp.AppEngineWebXmlParser()
+  backends_parser = backends_xml_parser.BackendsXmlParser()
+  web_parser = web_xml_parser.WebXmlParser()
+  app_engine_web_xml = aewx_parser.ProcessXml(app_engine_web_xml_str)
+  backends_xml = backends_parser.ProcessXml(backends_xml_str)
+  web_xml = web_parser.ProcessXml(web_xml_str)
+  translator = AppYamlTranslator(
+      app_engine_web_xml, backends_xml, web_xml, static_files, api_version)
+  return translator.GetYaml()
+
+
+def GetRuntime():
+
+  return 'java7'
+
+
+class AppYamlTranslator(object):
+  """Object that contains relevant information for generating app.yaml.
+
+  Attributes:
+    app_engine_web_xml: AppEngineWebXml object containing relevant information
+      from appengine-web.xml
+    backends_xml: BackendsXml object containing relevant info from backends.xml
+  """
+
+  def __init__(self,
+               app_engine_web_xml,
+               backends_xml,
+               web_xml,
+               static_files,
+               api_version):
+
+    self.app_engine_web_xml = app_engine_web_xml
+    self.backends_xml = backends_xml
+    self.web_xml = web_xml
+    self.static_files = static_files
+    self.api_version = api_version
+
+  def GetYaml(self):
+    """Returns full yaml text."""
+    self.VerifyRequiredEntriesPresent()
+    stmnt_list = self.TranslateBasicEntries()
+    stmnt_list += self.TranslateAutomaticScaling()
+    stmnt_list += self.TranslateBasicScaling()
+    stmnt_list += self.TranslateManualScaling()
+    stmnt_list += self.TranslatePrecompilationEnabled()
+    stmnt_list += self.TranslateInboundServices()
+    stmnt_list += self.TranslateAdminConsolePages()
+    stmnt_list += self.TranslateApiConfig()
+    stmnt_list += self.TranslatePagespeed()
+    stmnt_list += self.TranslateVmSettings()
+    stmnt_list += self.TranslateErrorHandlers()
+    stmnt_list += self.TranslateBackendsXml()
+    stmnt_list += self.TranslateApiVersion()
+    stmnt_list += self.TranslateHandlers()
+    return '\n'.join(stmnt_list) + '\n'
+
+  def SanitizeForYaml(self, the_string):
+    return "'%s'" % the_string.replace("'", "''")
+
+  def TranslateBasicEntries(self):
+    """Produces yaml for entries requiring little formatting."""
+    basic_statements = []
+
+    for entry_name, field in [
+        ('application', self.app_engine_web_xml.app_id),
+        ('source_language', self.app_engine_web_xml.source_language),
+        ('module', self.app_engine_web_xml.module),
+        ('version', self.app_engine_web_xml.version_id)]:
+      if field:
+        basic_statements.append(
+            '%s: %s' % (entry_name, self.SanitizeForYaml(field)))
+    for entry_name, field in [
+        ('runtime', GetRuntime()),
+        ('vm', self.app_engine_web_xml.use_vm),
+        ('threadsafe', self.app_engine_web_xml.threadsafe),
+        ('instance_class', self.app_engine_web_xml.instance_class),
+        ('auto_id_policy', self.app_engine_web_xml.auto_id_policy),
+        ('code_lock', self.app_engine_web_xml.codelock)]:
+      if field:
+        basic_statements.append('%s: %s' % (entry_name, field))
+    return basic_statements
+
+  def TranslateAutomaticScaling(self):
+    """Translates automatic scaling settings to yaml."""
+    if not self.app_engine_web_xml.automatic_scaling:
+      return []
+    statements = ['automatic_scaling:']
+    for setting in ['min_pending_latency',
+                    'max_pending_latency',
+                    'min_idle_instances',
+                    'max_idle_instances']:
+      value = getattr(self.app_engine_web_xml.automatic_scaling, setting)
+      if value:
+        statements.append('  %s: %s' % (setting, value))
+    return statements
+
+  def TranslateBasicScaling(self):
+    if not self.app_engine_web_xml.basic_scaling:
+      return []
+    statements = ['basic_scaling:']
+    statements.append('  max_instances: ' +
+                      self.app_engine_web_xml.basic_scaling.max_instances)
+    if self.app_engine_web_xml.basic_scaling.idle_timeout:
+      statements.append('  idle_timeout: ' +
+                        self.app_engine_web_xml.basic_scaling.idle_timeout)
+    return statements
+
+  def TranslateManualScaling(self):
+    if not self.app_engine_web_xml.manual_scaling:
+      return []
+
+    statements = ['manual_scaling:']
+    statements.append('  instances: ' +
+                      self.app_engine_web_xml.manual_scaling.instances)
+    return statements
+
+  def TranslatePrecompilationEnabled(self):
+    if self.app_engine_web_xml.precompilation_enabled:
+      return ['derived_file_type:', '- java_precompiled']
+    return []
+
+  def TranslateAdminConsolePages(self):
+    if not self.app_engine_web_xml.admin_console_pages:
+      return []
+    statements = ['admin_console:', '  pages:']
+    for admin_console_page in self.app_engine_web_xml.admin_console_pages:
+      statements.append('  - name: %s' % admin_console_page.name)
+      statements.append('    url: %s' % admin_console_page.url)
+    return statements
+
+  def TranslateApiConfig(self):
+
+    if not self.app_engine_web_xml.api_config:
+      return []
+    return ['api_config:', '  url: %s' % self.app_engine_web_xml.api_config.url,
+            '  script: unused']
+
+  def TranslateApiVersion(self):
+    return ['api_version: %s' % self.SanitizeForYaml(
+        self.api_version or NO_API_VERSION)]
+
+  def TranslatePagespeed(self):
+    """Translates pagespeed settings in appengine-web.xml to yaml."""
+    pagespeed = self.app_engine_web_xml.pagespeed
+    if not pagespeed:
+      return []
+    statements = ['pagespeed:']
+    for title, urls in [('domains_to_rewrite', pagespeed.domains_to_rewrite),
+                        ('url_blacklist', pagespeed.url_blacklist),
+                        ('enabled_rewriters', pagespeed.enabled_rewriters),
+                        ('disabled_rewriters', pagespeed.disabled_rewriters)]:
+      if urls:
+        statements.append('  %s:' % title)
+        statements += ['  - %s' % url for url in urls]
+    return statements
+
+  def TranslateVmSettings(self):
+    """Translates VM settings in appengine-web.xml to yaml."""
+    if (not self.app_engine_web_xml.use_vm or
+        not self.app_engine_web_xml.vm_settings):
+      return []
+
+    settings = self.app_engine_web_xml.vm_settings
+    statements = ['vm_settings:']
+    for name in sorted(settings):
+      statements.append(
+          '  %s: %s' % (
+              self.SanitizeForYaml(name), self.SanitizeForYaml(settings[name])))
+    return statements
+
+  def TranslateInboundServices(self):
+    services = self.app_engine_web_xml.inbound_services
+    if not services:
+      return []
+
+    statements = ['inbound_services:']
+    for service in sorted(services):
+      statements.append('- %s' % service)
+    return statements
+
+  def TranslateErrorHandlers(self):
+    """Translates error handlers specified in appengine-web.xml to yaml."""
+    if not self.app_engine_web_xml.static_error_handlers:
+      return []
+    statements = ['error_handlers:']
+    for error_handler in self.app_engine_web_xml.static_error_handlers:
+      name = error_handler.name
+      if not name.startswith('/'):
+        name = '/' + name
+
+      if ('__static__' + name) not in self.static_files:
+        raise AppEngineConfigException(
+            'No static file found for error handler: %s, out of %s' %
+            (name, self.static_files))
+      statements.append('- file: __static__%s' % name)
+      if error_handler.code:
+        statements.append('  error_code: %s' % error_handler.code)
+      mime_type = self.web_xml.GetMimeTypeForPath(name)
+      if mime_type:
+        statements.append('  mime_type: %s' % mime_type)
+
+    return statements
+
+  def TranslateBackendsXml(self):
+    """Translates backends.xml backends settings to yaml."""
+    if not self.backends_xml:
+      return []
+    statements = ['backends:']
+
+    for backend in self.backends_xml:
+      statements.append('- name: %s' % backend.name)
+      for entry, field in [('instances', backend.instances),
+                           ('instance_class', backend.instance_class),
+                           ('max_concurrent_requests',
+                            backend.max_concurrent_requests)]:
+        if field is not None:
+          statements.append('  %s: %s' % (entry, str(field)))
+
+      if backend.options:
+        options_str = ', '.join(sorted(list(backend.options)))
+        statements.append('  options: %s' % options_str)
+    return statements
+
+  def TranslateHandlers(self):
+    return handler_generator.GenerateYamlHandlersList(
+        self.app_engine_web_xml,
+        self.web_xml,
+        self.static_files)
+
+  def VerifyRequiredEntriesPresent(self):
+    if not all([self.app_engine_web_xml.app_id,
+                self.app_engine_web_xml.version_id,
+                GetRuntime(),
+                self.app_engine_web_xml.threadsafe_value_provided]):
+      raise AppEngineConfigException('Missing required fields')
diff --git a/google/net/proto2/proto/descriptor_pb2.py b/google/net/proto2/proto/descriptor_pb2.py
index cb2aefe..47f79f9 100644
--- a/google/net/proto2/proto/descriptor_pb2.py
+++ b/google/net/proto2/proto/descriptor_pb2.py
@@ -28,7 +28,7 @@
 DESCRIPTOR = _descriptor.FileDescriptor(
   name='net/proto2/proto/descriptor.proto',
   package='proto2',
-  serialized_pb='\n!net/proto2/proto/descriptor.proto\x12\x06proto2\">\n\x11\x46ileDescriptorSet\x12)\n\x04\x66ile\x18\x01 \x03(\x0b\x32\x1b.proto2.FileDescriptorProto\"\x95\x03\n\x13\x46ileDescriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07package\x18\x02 \x01(\t\x12\x12\n\ndependency\x18\x03 \x03(\t\x12\x19\n\x11public_dependency\x18\n \x03(\x05\x12\x17\n\x0fweak_dependency\x18\x0b \x03(\x05\x12-\n\x0cmessage_type\x18\x04 \x03(\x0b\x32\x17.proto2.DescriptorProto\x12.\n\tenum_type\x18\x05 \x03(\x0b\x32\x1b.proto2.EnumDescriptorProto\x12/\n\x07service\x18\x06 \x03(\x0b\x32\x1e.proto2.ServiceDescriptorProto\x12/\n\textension\x18\x07 \x03(\x0b\x32\x1c.proto2.FieldDescriptorProto\x12$\n\x07options\x18\x08 \x01(\x0b\x32\x13.proto2.FileOptions\x12\x30\n\x10source_code_info\x18\t \x01(\x0b\x32\x16.proto2.SourceCodeInfo\"\xa5\x03\n\x0f\x44\x65scriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12+\n\x05\x66ield\x18\x02 \x03(\x0b\x32\x1c.proto2.FieldDescriptorProto\x12/\n\textension\x18\x06 \x03(\x0b\x32\x1c.proto2.FieldDescriptorProto\x12,\n\x0bnested_type\x18\x03 \x03(\x0b\x32\x17.proto2.DescriptorProto\x12.\n\tenum_type\x18\x04 \x03(\x0b\x32\x1b.proto2.EnumDescriptorProto\x12?\n\x0f\x65xtension_range\x18\x05 \x03(\x0b\x32&.proto2.DescriptorProto.ExtensionRange\x12\x30\n\noneof_decl\x18\x08 \x03(\x0b\x32\x1c.proto2.OneofDescriptorProto\x12\'\n\x07options\x18\x07 \x01(\x0b\x32\x16.proto2.MessageOptions\x1a,\n\x0e\x45xtensionRange\x12\r\n\x05start\x18\x01 \x01(\x05\x12\x0b\n\x03\x65nd\x18\x02 \x01(\x05\"\x8e\x05\n\x14\x46ieldDescriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06number\x18\x03 \x01(\x05\x12\x31\n\x05label\x18\x04 \x01(\x0e\x32\".proto2.FieldDescriptorProto.Label\x12/\n\x04type\x18\x05 \x01(\x0e\x32!.proto2.FieldDescriptorProto.Type\x12\x11\n\ttype_name\x18\x06 \x01(\t\x12\x10\n\x08\x65xtendee\x18\x02 \x01(\t\x12\x15\n\rdefault_value\x18\x07 \x01(\t\x12\x13\n\x0boneof_index\x18\t \x01(\x05\x12%\n\x07options\x18\x08 \x01(\x0b\x32\x14.proto2.FieldOptions\"\xb6\x02\n\x04Type\x12\x0f\n\x0bTYPE_DOUBLE\x10\x01\x12\x0e\n\nTYPE_FLOAT\x10\x02\x12\x0e\n\nTYPE_INT64\x10\x03\x12\x0f\n\x0bTYPE_UINT64\x10\x04\x12\x0e\n\nTYPE_INT32\x10\x05\x12\x10\n\x0cTYPE_FIXED64\x10\x06\x12\x10\n\x0cTYPE_FIXED32\x10\x07\x12\r\n\tTYPE_BOOL\x10\x08\x12\x0f\n\x0bTYPE_STRING\x10\t\x12\x0e\n\nTYPE_GROUP\x10\n\x12\x10\n\x0cTYPE_MESSAGE\x10\x0b\x12\x0e\n\nTYPE_BYTES\x10\x0c\x12\x0f\n\x0bTYPE_UINT32\x10\r\x12\r\n\tTYPE_ENUM\x10\x0e\x12\x11\n\rTYPE_SFIXED32\x10\x0f\x12\x11\n\rTYPE_SFIXED64\x10\x10\x12\x0f\n\x0bTYPE_SINT32\x10\x11\x12\x0f\n\x0bTYPE_SINT64\x10\x12\"C\n\x05Label\x12\x12\n\x0eLABEL_OPTIONAL\x10\x01\x12\x12\n\x0eLABEL_REQUIRED\x10\x02\x12\x12\n\x0eLABEL_REPEATED\x10\x03\"$\n\x14OneofDescriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\"z\n\x13\x45numDescriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12/\n\x05value\x18\x02 \x03(\x0b\x32 .proto2.EnumValueDescriptorProto\x12$\n\x07options\x18\x03 \x01(\x0b\x32\x13.proto2.EnumOptions\"c\n\x18\x45numValueDescriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06number\x18\x02 \x01(\x05\x12)\n\x07options\x18\x03 \x01(\x0b\x32\x18.proto2.EnumValueOptions\"\xad\x01\n\x16ServiceDescriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12-\n\x06method\x18\x02 \x03(\x0b\x32\x1d.proto2.MethodDescriptorProto\x12-\n\x06stream\x18\x04 \x03(\x0b\x32\x1d.proto2.StreamDescriptorProto\x12\'\n\x07options\x18\x03 \x01(\x0b\x32\x16.proto2.ServiceOptions\"v\n\x15MethodDescriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x12\n\ninput_type\x18\x02 \x01(\t\x12\x13\n\x0boutput_type\x18\x03 \x01(\t\x12&\n\x07options\x18\x04 \x01(\x0b\x32\x15.proto2.MethodOptions\"\x87\x01\n\x15StreamDescriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x1b\n\x13\x63lient_message_type\x18\x02 \x01(\t\x12\x1b\n\x13server_message_type\x18\x03 \x01(\t\x12&\n\x07options\x18\x04 \x01(\x0b\x32\x15.proto2.StreamOptions\"\xba\t\n\x0b\x46ileOptions\x12\x19\n\x0e\x63\x63_api_version\x18\x02 \x01(\x05:\x01\x32\x12V\n\x14\x63\x63_api_compatibility\x18\x0f \x01(\x0e\x32&.proto2.FileOptions.CompatibilityLevel:\x10NO_COMPATIBILITY\x12\'\n\x19\x63\x63_proto_array_compatible\x18\x16 \x01(\x08:\x04true\x12\"\n\x14\x63\x63_utf8_verification\x18\x18 \x01(\x08:\x04true\x12$\n\x15\x63\x63_proto1_text_format\x18\x19 \x01(\x08:\x05\x66\x61lse\x12\x14\n\x0cjava_package\x18\x01 \x01(\t\x12\x19\n\x0epy_api_version\x18\x04 \x01(\x05:\x01\x32\x12\x1b\n\x10java_api_version\x18\x05 \x01(\x05:\x01\x32\x12!\n\x13java_use_javaproto2\x18\x06 \x01(\x08:\x04true\x12\x1e\n\x10java_java5_enums\x18\x07 \x01(\x08:\x04true\x12)\n\x1ajava_generate_rpc_baseimpl\x18\r \x01(\x08:\x05\x66\x61lse\x12#\n\x14java_use_javastrings\x18\x15 \x01(\x08:\x05\x66\x61lse\x12\x1c\n\x14java_alt_api_package\x18\x13 \x01(\t\x12\x33\n%java_enable_dual_generate_mutable_api\x18\x1a \x01(\x08:\x04true\x12\x1c\n\x14java_outer_classname\x18\x08 \x01(\t\x12\"\n\x13java_multiple_files\x18\n \x01(\x08:\x05\x66\x61lse\x12,\n\x1djava_generate_equals_and_hash\x18\x14 \x01(\x08:\x05\x66\x61lse\x12%\n\x16java_string_check_utf8\x18\x1b \x01(\x08:\x05\x66\x61lse\x12=\n\x0coptimize_for\x18\t \x01(\x0e\x32 .proto2.FileOptions.OptimizeMode:\x05SPEED\x12\x12\n\ngo_package\x18\x0b \x01(\t\x12\x1a\n\x12javascript_package\x18\x0c \x01(\t\x12\x1a\n\x0fszl_api_version\x18\x0e \x01(\x05:\x01\x31\x12\"\n\x13\x63\x63_generic_services\x18\x10 \x01(\x08:\x05\x66\x61lse\x12$\n\x15java_generic_services\x18\x11 \x01(\x08:\x05\x66\x61lse\x12\"\n\x13py_generic_services\x18\x12 \x01(\x08:\x05\x66\x61lse\x12\x19\n\ndeprecated\x18\x17 \x01(\x08:\x05\x66\x61lse\x12:\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32\x1b.proto2.UninterpretedOption\"c\n\x12\x43ompatibilityLevel\x12\x14\n\x10NO_COMPATIBILITY\x10\x00\x12\x15\n\x11PROTO1_COMPATIBLE\x10\x64\x12 \n\x1c\x44\x45PRECATED_PROTO1_COMPATIBLE\x10\x32\":\n\x0cOptimizeMode\x12\t\n\x05SPEED\x10\x01\x12\r\n\tCODE_SIZE\x10\x02\x12\x10\n\x0cLITE_RUNTIME\x10\x03*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xca\x01\n\x0eMessageOptions\x12&\n\x17message_set_wire_format\x18\x01 \x01(\x08:\x05\x66\x61lse\x12.\n\x1fno_standard_descriptor_accessor\x18\x02 \x01(\x08:\x05\x66\x61lse\x12\x19\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lse\x12:\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32\x1b.proto2.UninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xf9\x04\n\x0c\x46ieldOptions\x12\x31\n\x05\x63type\x18\x01 \x01(\x0e\x32\x1a.proto2.FieldOptions.CType:\x06STRING\x12\x0e\n\x06packed\x18\x02 \x01(\x08\x12\x31\n\x05jtype\x18\x04 \x01(\x0e\x32\x1a.proto2.FieldOptions.JType:\x06NORMAL\x12\x36\n\x06jstype\x18\x06 \x01(\x0e\x32\x1b.proto2.FieldOptions.JSType:\tJS_NORMAL\x12\x13\n\x04lazy\x18\x05 \x01(\x08:\x05\x66\x61lse\x12\x19\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lse\x12\x1c\n\x14\x65xperimental_map_key\x18\t \x01(\t\x12\x13\n\x04weak\x18\n \x01(\x08:\x05\x66\x61lse\x12<\n\x0fupgraded_option\x18\x0b \x03(\x0b\x32#.proto2.FieldOptions.UpgradedOption\x12:\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32\x1b.proto2.UninterpretedOption\x1a-\n\x0eUpgradedOption\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\"/\n\x05\x43Type\x12\n\n\x06STRING\x10\x00\x12\x08\n\x04\x43ORD\x10\x01\x12\x10\n\x0cSTRING_PIECE\x10\x02\"<\n\x05JType\x12\n\n\x06NORMAL\x10\x00\x12\t\n\x05\x42YTES\x10\x01\x12\x1c\n\x18\x45XPERIMENTAL_BYTE_BUFFER\x10\x02\"5\n\x06JSType\x12\r\n\tJS_NORMAL\x10\x00\x12\r\n\tJS_STRING\x10\x01\x12\r\n\tJS_NUMBER\x10\x02*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\x99\x01\n\x0b\x45numOptions\x12\x13\n\x0bproto1_name\x18\x01 \x01(\t\x12\x13\n\x0b\x61llow_alias\x18\x02 \x01(\x08\x12\x19\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lse\x12:\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32\x1b.proto2.UninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"t\n\x10\x45numValueOptions\x12\x19\n\ndeprecated\x18\x01 \x01(\x08:\x05\x66\x61lse\x12:\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32\x1b.proto2.UninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xb6\x01\n\x0eServiceOptions\x12\x1d\n\x0emulticast_stub\x18\x14 \x01(\x08:\x05\x66\x61lse\x12#\n\x17\x66\x61ilure_detection_delay\x18\x10 \x01(\x01:\x02-1\x12\x19\n\ndeprecated\x18! \x01(\x08:\x05\x66\x61lse\x12:\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32\x1b.proto2.UninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xd4\x06\n\rMethodOptions\x12\x35\n\x08protocol\x18\x07 \x01(\x0e\x32\x1e.proto2.MethodOptions.Protocol:\x03TCP\x12\x14\n\x08\x64\x65\x61\x64line\x18\x08 \x01(\x01:\x02-1\x12$\n\x15\x64uplicate_suppression\x18\t \x01(\x08:\x05\x66\x61lse\x12\x18\n\tfail_fast\x18\n \x01(\x08:\x05\x66\x61lse\x12\x1b\n\x0e\x63lient_logging\x18\x0b \x01(\x11:\x03\x32\x35\x36\x12\x1b\n\x0eserver_logging\x18\x0c \x01(\x11:\x03\x32\x35\x36\x12\x41\n\x0esecurity_level\x18\r \x01(\x0e\x32#.proto2.MethodOptions.SecurityLevel:\x04NONE\x12\x43\n\x0fresponse_format\x18\x0f \x01(\x0e\x32\x1c.proto2.MethodOptions.Format:\x0cUNCOMPRESSED\x12\x42\n\x0erequest_format\x18\x11 \x01(\x0e\x32\x1c.proto2.MethodOptions.Format:\x0cUNCOMPRESSED\x12\x13\n\x0bstream_type\x18\x12 \x01(\t\x12\x16\n\x0esecurity_label\x18\x13 \x01(\t\x12\x18\n\x10\x63lient_streaming\x18\x14 \x01(\x08\x12\x18\n\x10server_streaming\x18\x15 \x01(\x08\x12\x1a\n\x12legacy_stream_type\x18\x16 \x01(\t\x12\x1a\n\x12legacy_result_type\x18\x17 \x01(\t\x12\x19\n\ndeprecated\x18! \x01(\x08:\x05\x66\x61lse\x12:\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32\x1b.proto2.UninterpretedOption\"\x1c\n\x08Protocol\x12\x07\n\x03TCP\x10\x00\x12\x07\n\x03UDP\x10\x01\"e\n\rSecurityLevel\x12\x08\n\x04NONE\x10\x00\x12\r\n\tINTEGRITY\x10\x01\x12\x19\n\x15PRIVACY_AND_INTEGRITY\x10\x02\x12 \n\x1cSTRONG_PRIVACY_AND_INTEGRITY\x10\x03\"0\n\x06\x46ormat\x12\x10\n\x0cUNCOMPRESSED\x10\x00\x12\x14\n\x10ZIPPY_COMPRESSED\x10\x01*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xde\x03\n\rStreamOptions\x12!\n\x15\x63lient_initial_tokens\x18\x01 \x01(\x03:\x02-1\x12!\n\x15server_initial_tokens\x18\x02 \x01(\x03:\x02-1\x12<\n\ntoken_unit\x18\x03 \x01(\x0e\x32\x1f.proto2.StreamOptions.TokenUnit:\x07MESSAGE\x12\x41\n\x0esecurity_level\x18\x04 \x01(\x0e\x32#.proto2.MethodOptions.SecurityLevel:\x04NONE\x12\x16\n\x0esecurity_label\x18\x05 \x01(\t\x12\x1b\n\x0e\x63lient_logging\x18\x06 \x01(\x05:\x03\x32\x35\x36\x12\x1b\n\x0eserver_logging\x18\x07 \x01(\x05:\x03\x32\x35\x36\x12\x14\n\x08\x64\x65\x61\x64line\x18\x08 \x01(\x01:\x02-1\x12\x18\n\tfail_fast\x18\t \x01(\x08:\x05\x66\x61lse\x12\x19\n\ndeprecated\x18! \x01(\x08:\x05\x66\x61lse\x12:\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32\x1b.proto2.UninterpretedOption\"\"\n\tTokenUnit\x12\x0b\n\x07MESSAGE\x10\x00\x12\x08\n\x04\x42YTE\x10\x01*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\x95\x02\n\x13UninterpretedOption\x12\x32\n\x04name\x18\x02 \x03(\x0b\x32$.proto2.UninterpretedOption.NamePart\x12\x18\n\x10identifier_value\x18\x03 \x01(\t\x12\x1a\n\x12positive_int_value\x18\x04 \x01(\x04\x12\x1a\n\x12negative_int_value\x18\x05 \x01(\x03\x12\x14\n\x0c\x64ouble_value\x18\x06 \x01(\x01\x12\x14\n\x0cstring_value\x18\x07 \x01(\x0c\x12\x17\n\x0f\x61ggregate_value\x18\x08 \x01(\t\x1a\x33\n\x08NamePart\x12\x11\n\tname_part\x18\x01 \x02(\t\x12\x14\n\x0cis_extension\x18\x02 \x02(\x08\"\xa8\x01\n\x0eSourceCodeInfo\x12\x31\n\x08location\x18\x01 \x03(\x0b\x32\x1f.proto2.SourceCodeInfo.Location\x1a\x63\n\x08Location\x12\x10\n\x04path\x18\x01 \x03(\x05\x42\x02\x10\x01\x12\x10\n\x04span\x18\x02 \x03(\x05\x42\x02\x10\x01\x12\x18\n\x10leading_comments\x18\x03 \x01(\t\x12\x19\n\x11trailing_comments\x18\x04 \x01(\tB)\n\x13\x63om.google.protobufB\x10\x44\x65scriptorProtosH\x01')
+  serialized_pb='\n!net/proto2/proto/descriptor.proto\x12\x06proto2\">\n\x11\x46ileDescriptorSet\x12)\n\x04\x66ile\x18\x01 \x03(\x0b\x32\x1b.proto2.FileDescriptorProto\"\x95\x03\n\x13\x46ileDescriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07package\x18\x02 \x01(\t\x12\x12\n\ndependency\x18\x03 \x03(\t\x12\x19\n\x11public_dependency\x18\n \x03(\x05\x12\x17\n\x0fweak_dependency\x18\x0b \x03(\x05\x12-\n\x0cmessage_type\x18\x04 \x03(\x0b\x32\x17.proto2.DescriptorProto\x12.\n\tenum_type\x18\x05 \x03(\x0b\x32\x1b.proto2.EnumDescriptorProto\x12/\n\x07service\x18\x06 \x03(\x0b\x32\x1e.proto2.ServiceDescriptorProto\x12/\n\textension\x18\x07 \x03(\x0b\x32\x1c.proto2.FieldDescriptorProto\x12$\n\x07options\x18\x08 \x01(\x0b\x32\x13.proto2.FileOptions\x12\x30\n\x10source_code_info\x18\t \x01(\x0b\x32\x16.proto2.SourceCodeInfo\"\xa5\x03\n\x0f\x44\x65scriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12+\n\x05\x66ield\x18\x02 \x03(\x0b\x32\x1c.proto2.FieldDescriptorProto\x12/\n\textension\x18\x06 \x03(\x0b\x32\x1c.proto2.FieldDescriptorProto\x12,\n\x0bnested_type\x18\x03 \x03(\x0b\x32\x17.proto2.DescriptorProto\x12.\n\tenum_type\x18\x04 \x03(\x0b\x32\x1b.proto2.EnumDescriptorProto\x12?\n\x0f\x65xtension_range\x18\x05 \x03(\x0b\x32&.proto2.DescriptorProto.ExtensionRange\x12\x30\n\noneof_decl\x18\x08 \x03(\x0b\x32\x1c.proto2.OneofDescriptorProto\x12\'\n\x07options\x18\x07 \x01(\x0b\x32\x16.proto2.MessageOptions\x1a,\n\x0e\x45xtensionRange\x12\r\n\x05start\x18\x01 \x01(\x05\x12\x0b\n\x03\x65nd\x18\x02 \x01(\x05\"\x8e\x05\n\x14\x46ieldDescriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06number\x18\x03 \x01(\x05\x12\x31\n\x05label\x18\x04 \x01(\x0e\x32\".proto2.FieldDescriptorProto.Label\x12/\n\x04type\x18\x05 \x01(\x0e\x32!.proto2.FieldDescriptorProto.Type\x12\x11\n\ttype_name\x18\x06 \x01(\t\x12\x10\n\x08\x65xtendee\x18\x02 \x01(\t\x12\x15\n\rdefault_value\x18\x07 \x01(\t\x12\x13\n\x0boneof_index\x18\t \x01(\x05\x12%\n\x07options\x18\x08 \x01(\x0b\x32\x14.proto2.FieldOptions\"\xb6\x02\n\x04Type\x12\x0f\n\x0bTYPE_DOUBLE\x10\x01\x12\x0e\n\nTYPE_FLOAT\x10\x02\x12\x0e\n\nTYPE_INT64\x10\x03\x12\x0f\n\x0bTYPE_UINT64\x10\x04\x12\x0e\n\nTYPE_INT32\x10\x05\x12\x10\n\x0cTYPE_FIXED64\x10\x06\x12\x10\n\x0cTYPE_FIXED32\x10\x07\x12\r\n\tTYPE_BOOL\x10\x08\x12\x0f\n\x0bTYPE_STRING\x10\t\x12\x0e\n\nTYPE_GROUP\x10\n\x12\x10\n\x0cTYPE_MESSAGE\x10\x0b\x12\x0e\n\nTYPE_BYTES\x10\x0c\x12\x0f\n\x0bTYPE_UINT32\x10\r\x12\r\n\tTYPE_ENUM\x10\x0e\x12\x11\n\rTYPE_SFIXED32\x10\x0f\x12\x11\n\rTYPE_SFIXED64\x10\x10\x12\x0f\n\x0bTYPE_SINT32\x10\x11\x12\x0f\n\x0bTYPE_SINT64\x10\x12\"C\n\x05Label\x12\x12\n\x0eLABEL_OPTIONAL\x10\x01\x12\x12\n\x0eLABEL_REQUIRED\x10\x02\x12\x12\n\x0eLABEL_REPEATED\x10\x03\"$\n\x14OneofDescriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\"z\n\x13\x45numDescriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12/\n\x05value\x18\x02 \x03(\x0b\x32 .proto2.EnumValueDescriptorProto\x12$\n\x07options\x18\x03 \x01(\x0b\x32\x13.proto2.EnumOptions\"c\n\x18\x45numValueDescriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0e\n\x06number\x18\x02 \x01(\x05\x12)\n\x07options\x18\x03 \x01(\x0b\x32\x18.proto2.EnumValueOptions\"\xad\x01\n\x16ServiceDescriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12-\n\x06method\x18\x02 \x03(\x0b\x32\x1d.proto2.MethodDescriptorProto\x12-\n\x06stream\x18\x04 \x03(\x0b\x32\x1d.proto2.StreamDescriptorProto\x12\'\n\x07options\x18\x03 \x01(\x0b\x32\x16.proto2.ServiceOptions\"v\n\x15MethodDescriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x12\n\ninput_type\x18\x02 \x01(\t\x12\x13\n\x0boutput_type\x18\x03 \x01(\t\x12&\n\x07options\x18\x04 \x01(\x0b\x32\x15.proto2.MethodOptions\"\x87\x01\n\x15StreamDescriptorProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x1b\n\x13\x63lient_message_type\x18\x02 \x01(\t\x12\x1b\n\x13server_message_type\x18\x03 \x01(\t\x12&\n\x07options\x18\x04 \x01(\x0b\x32\x15.proto2.StreamOptions\"\xba\t\n\x0b\x46ileOptions\x12\x19\n\x0e\x63\x63_api_version\x18\x02 \x01(\x05:\x01\x32\x12V\n\x14\x63\x63_api_compatibility\x18\x0f \x01(\x0e\x32&.proto2.FileOptions.CompatibilityLevel:\x10NO_COMPATIBILITY\x12\'\n\x19\x63\x63_proto_array_compatible\x18\x16 \x01(\x08:\x04true\x12\"\n\x14\x63\x63_utf8_verification\x18\x18 \x01(\x08:\x04true\x12$\n\x15\x63\x63_proto1_text_format\x18\x19 \x01(\x08:\x05\x66\x61lse\x12\x14\n\x0cjava_package\x18\x01 \x01(\t\x12\x19\n\x0epy_api_version\x18\x04 \x01(\x05:\x01\x32\x12\x1b\n\x10java_api_version\x18\x05 \x01(\x05:\x01\x32\x12!\n\x13java_use_javaproto2\x18\x06 \x01(\x08:\x04true\x12\x1e\n\x10java_java5_enums\x18\x07 \x01(\x08:\x04true\x12)\n\x1ajava_generate_rpc_baseimpl\x18\r \x01(\x08:\x05\x66\x61lse\x12#\n\x14java_use_javastrings\x18\x15 \x01(\x08:\x05\x66\x61lse\x12\x1c\n\x14java_alt_api_package\x18\x13 \x01(\t\x12\x33\n%java_enable_dual_generate_mutable_api\x18\x1a \x01(\x08:\x04true\x12\x1c\n\x14java_outer_classname\x18\x08 \x01(\t\x12\"\n\x13java_multiple_files\x18\n \x01(\x08:\x05\x66\x61lse\x12,\n\x1djava_generate_equals_and_hash\x18\x14 \x01(\x08:\x05\x66\x61lse\x12%\n\x16java_string_check_utf8\x18\x1b \x01(\x08:\x05\x66\x61lse\x12=\n\x0coptimize_for\x18\t \x01(\x0e\x32 .proto2.FileOptions.OptimizeMode:\x05SPEED\x12\x12\n\ngo_package\x18\x0b \x01(\t\x12\x1a\n\x12javascript_package\x18\x0c \x01(\t\x12\x1a\n\x0fszl_api_version\x18\x0e \x01(\x05:\x01\x31\x12\"\n\x13\x63\x63_generic_services\x18\x10 \x01(\x08:\x05\x66\x61lse\x12$\n\x15java_generic_services\x18\x11 \x01(\x08:\x05\x66\x61lse\x12\"\n\x13py_generic_services\x18\x12 \x01(\x08:\x05\x66\x61lse\x12\x19\n\ndeprecated\x18\x17 \x01(\x08:\x05\x66\x61lse\x12:\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32\x1b.proto2.UninterpretedOption\"c\n\x12\x43ompatibilityLevel\x12\x14\n\x10NO_COMPATIBILITY\x10\x00\x12\x15\n\x11PROTO1_COMPATIBLE\x10\x64\x12 \n\x1c\x44\x45PRECATED_PROTO1_COMPATIBLE\x10\x32\":\n\x0cOptimizeMode\x12\t\n\x05SPEED\x10\x01\x12\r\n\tCODE_SIZE\x10\x02\x12\x10\n\x0cLITE_RUNTIME\x10\x03*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xd1\x02\n\x0eMessageOptions\x12+\n#experimental_java_message_interface\x18\x04 \x03(\t\x12+\n#experimental_java_builder_interface\x18\x05 \x03(\t\x12+\n#experimental_java_interface_extends\x18\x06 \x03(\t\x12&\n\x17message_set_wire_format\x18\x01 \x01(\x08:\x05\x66\x61lse\x12.\n\x1fno_standard_descriptor_accessor\x18\x02 \x01(\x08:\x05\x66\x61lse\x12\x19\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lse\x12:\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32\x1b.proto2.UninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xf9\x04\n\x0c\x46ieldOptions\x12\x31\n\x05\x63type\x18\x01 \x01(\x0e\x32\x1a.proto2.FieldOptions.CType:\x06STRING\x12\x0e\n\x06packed\x18\x02 \x01(\x08\x12\x31\n\x05jtype\x18\x04 \x01(\x0e\x32\x1a.proto2.FieldOptions.JType:\x06NORMAL\x12\x36\n\x06jstype\x18\x06 \x01(\x0e\x32\x1b.proto2.FieldOptions.JSType:\tJS_NORMAL\x12\x13\n\x04lazy\x18\x05 \x01(\x08:\x05\x66\x61lse\x12\x19\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lse\x12\x1c\n\x14\x65xperimental_map_key\x18\t \x01(\t\x12\x13\n\x04weak\x18\n \x01(\x08:\x05\x66\x61lse\x12<\n\x0fupgraded_option\x18\x0b \x03(\x0b\x32#.proto2.FieldOptions.UpgradedOption\x12:\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32\x1b.proto2.UninterpretedOption\x1a-\n\x0eUpgradedOption\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\"/\n\x05\x43Type\x12\n\n\x06STRING\x10\x00\x12\x08\n\x04\x43ORD\x10\x01\x12\x10\n\x0cSTRING_PIECE\x10\x02\"<\n\x05JType\x12\n\n\x06NORMAL\x10\x00\x12\t\n\x05\x42YTES\x10\x01\x12\x1c\n\x18\x45XPERIMENTAL_BYTE_BUFFER\x10\x02\"5\n\x06JSType\x12\r\n\tJS_NORMAL\x10\x00\x12\r\n\tJS_STRING\x10\x01\x12\r\n\tJS_NUMBER\x10\x02*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\x99\x01\n\x0b\x45numOptions\x12\x13\n\x0bproto1_name\x18\x01 \x01(\t\x12\x13\n\x0b\x61llow_alias\x18\x02 \x01(\x08\x12\x19\n\ndeprecated\x18\x03 \x01(\x08:\x05\x66\x61lse\x12:\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32\x1b.proto2.UninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"t\n\x10\x45numValueOptions\x12\x19\n\ndeprecated\x18\x01 \x01(\x08:\x05\x66\x61lse\x12:\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32\x1b.proto2.UninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xb6\x01\n\x0eServiceOptions\x12\x1d\n\x0emulticast_stub\x18\x14 \x01(\x08:\x05\x66\x61lse\x12#\n\x17\x66\x61ilure_detection_delay\x18\x10 \x01(\x01:\x02-1\x12\x19\n\ndeprecated\x18! \x01(\x08:\x05\x66\x61lse\x12:\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32\x1b.proto2.UninterpretedOption*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xa8\x07\n\rMethodOptions\x12\x35\n\x08protocol\x18\x07 \x01(\x0e\x32\x1e.proto2.MethodOptions.Protocol:\x03TCP\x12\x14\n\x08\x64\x65\x61\x64line\x18\x08 \x01(\x01:\x02-1\x12$\n\x15\x64uplicate_suppression\x18\t \x01(\x08:\x05\x66\x61lse\x12\x18\n\tfail_fast\x18\n \x01(\x08:\x05\x66\x61lse\x12\x1b\n\x0e\x63lient_logging\x18\x0b \x01(\x11:\x03\x32\x35\x36\x12\x1b\n\x0eserver_logging\x18\x0c \x01(\x11:\x03\x32\x35\x36\x12\x41\n\x0esecurity_level\x18\r \x01(\x0e\x32#.proto2.MethodOptions.SecurityLevel:\x04NONE\x12\x43\n\x0fresponse_format\x18\x0f \x01(\x0e\x32\x1c.proto2.MethodOptions.Format:\x0cUNCOMPRESSED\x12\x42\n\x0erequest_format\x18\x11 \x01(\x0e\x32\x1c.proto2.MethodOptions.Format:\x0cUNCOMPRESSED\x12\x13\n\x0bstream_type\x18\x12 \x01(\t\x12\x16\n\x0esecurity_label\x18\x13 \x01(\t\x12\x18\n\x10\x63lient_streaming\x18\x14 \x01(\x08\x12\x18\n\x10server_streaming\x18\x15 \x01(\x08\x12\x1a\n\x12legacy_stream_type\x18\x16 \x01(\t\x12\x1a\n\x12legacy_result_type\x18\x17 \x01(\t\x12(\n\x1clegacy_client_initial_tokens\x18\x18 \x01(\x03:\x02-1\x12(\n\x1clegacy_server_initial_tokens\x18\x19 \x01(\x03:\x02-1\x12\x19\n\ndeprecated\x18! \x01(\x08:\x05\x66\x61lse\x12:\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32\x1b.proto2.UninterpretedOption\"\x1c\n\x08Protocol\x12\x07\n\x03TCP\x10\x00\x12\x07\n\x03UDP\x10\x01\"e\n\rSecurityLevel\x12\x08\n\x04NONE\x10\x00\x12\r\n\tINTEGRITY\x10\x01\x12\x19\n\x15PRIVACY_AND_INTEGRITY\x10\x02\x12 \n\x1cSTRONG_PRIVACY_AND_INTEGRITY\x10\x03\"0\n\x06\x46ormat\x12\x10\n\x0cUNCOMPRESSED\x10\x00\x12\x14\n\x10ZIPPY_COMPRESSED\x10\x01*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\xde\x03\n\rStreamOptions\x12!\n\x15\x63lient_initial_tokens\x18\x01 \x01(\x03:\x02-1\x12!\n\x15server_initial_tokens\x18\x02 \x01(\x03:\x02-1\x12<\n\ntoken_unit\x18\x03 \x01(\x0e\x32\x1f.proto2.StreamOptions.TokenUnit:\x07MESSAGE\x12\x41\n\x0esecurity_level\x18\x04 \x01(\x0e\x32#.proto2.MethodOptions.SecurityLevel:\x04NONE\x12\x16\n\x0esecurity_label\x18\x05 \x01(\t\x12\x1b\n\x0e\x63lient_logging\x18\x06 \x01(\x05:\x03\x32\x35\x36\x12\x1b\n\x0eserver_logging\x18\x07 \x01(\x05:\x03\x32\x35\x36\x12\x14\n\x08\x64\x65\x61\x64line\x18\x08 \x01(\x01:\x02-1\x12\x18\n\tfail_fast\x18\t \x01(\x08:\x05\x66\x61lse\x12\x19\n\ndeprecated\x18! \x01(\x08:\x05\x66\x61lse\x12:\n\x14uninterpreted_option\x18\xe7\x07 \x03(\x0b\x32\x1b.proto2.UninterpretedOption\"\"\n\tTokenUnit\x12\x0b\n\x07MESSAGE\x10\x00\x12\x08\n\x04\x42YTE\x10\x01*\t\x08\xe8\x07\x10\x80\x80\x80\x80\x02\"\x95\x02\n\x13UninterpretedOption\x12\x32\n\x04name\x18\x02 \x03(\x0b\x32$.proto2.UninterpretedOption.NamePart\x12\x18\n\x10identifier_value\x18\x03 \x01(\t\x12\x1a\n\x12positive_int_value\x18\x04 \x01(\x04\x12\x1a\n\x12negative_int_value\x18\x05 \x01(\x03\x12\x14\n\x0c\x64ouble_value\x18\x06 \x01(\x01\x12\x14\n\x0cstring_value\x18\x07 \x01(\x0c\x12\x17\n\x0f\x61ggregate_value\x18\x08 \x01(\t\x1a\x33\n\x08NamePart\x12\x11\n\tname_part\x18\x01 \x02(\t\x12\x14\n\x0cis_extension\x18\x02 \x02(\x08\"\xa8\x01\n\x0eSourceCodeInfo\x12\x31\n\x08location\x18\x01 \x03(\x0b\x32\x1f.proto2.SourceCodeInfo.Location\x1a\x63\n\x08Location\x12\x10\n\x04path\x18\x01 \x03(\x05\x42\x02\x10\x01\x12\x10\n\x04span\x18\x02 \x03(\x05\x42\x02\x10\x01\x12\x18\n\x10leading_comments\x18\x03 \x01(\t\x12\x19\n\x11trailing_comments\x18\x04 \x01(\tB)\n\x13\x63om.google.protobufB\x10\x44\x65scriptorProtosH\x01')
 
 
 
@@ -213,8 +213,8 @@
   ],
   containing_type=None,
   options=None,
-  serialized_start=4172,
-  serialized_end=4219,
+  serialized_start=4307,
+  serialized_end=4354,
 )
 
 _FIELDOPTIONS_JTYPE = _descriptor.EnumDescriptor(
@@ -238,8 +238,8 @@
   ],
   containing_type=None,
   options=None,
-  serialized_start=4221,
-  serialized_end=4281,
+  serialized_start=4356,
+  serialized_end=4416,
 )
 
 _FIELDOPTIONS_JSTYPE = _descriptor.EnumDescriptor(
@@ -263,8 +263,8 @@
   ],
   containing_type=None,
   options=None,
-  serialized_start=4283,
-  serialized_end=4336,
+  serialized_start=4418,
+  serialized_end=4471,
 )
 
 _METHODOPTIONS_PROTOCOL = _descriptor.EnumDescriptor(
@@ -284,8 +284,8 @@
   ],
   containing_type=None,
   options=None,
-  serialized_start=5469,
-  serialized_end=5497,
+  serialized_start=5688,
+  serialized_end=5716,
 )
 
 _METHODOPTIONS_SECURITYLEVEL = _descriptor.EnumDescriptor(
@@ -313,8 +313,8 @@
   ],
   containing_type=None,
   options=None,
-  serialized_start=5499,
-  serialized_end=5600,
+  serialized_start=5718,
+  serialized_end=5819,
 )
 
 _METHODOPTIONS_FORMAT = _descriptor.EnumDescriptor(
@@ -334,8 +334,8 @@
   ],
   containing_type=None,
   options=None,
-  serialized_start=5602,
-  serialized_end=5650,
+  serialized_start=5821,
+  serialized_end=5869,
 )
 
 _STREAMOPTIONS_TOKENUNIT = _descriptor.EnumDescriptor(
@@ -355,8 +355,8 @@
   ],
   containing_type=None,
   options=None,
-  serialized_start=6097,
-  serialized_end=6131,
+  serialized_start=6316,
+  serialized_end=6350,
 )
 
 
@@ -1162,28 +1162,49 @@
   containing_type=None,
   fields=[
     _descriptor.FieldDescriptor(
-      name='message_set_wire_format', full_name='proto2.MessageOptions.message_set_wire_format', index=0,
+      name='experimental_java_message_interface', full_name='proto2.MessageOptions.experimental_java_message_interface', index=0,
+      number=4, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='experimental_java_builder_interface', full_name='proto2.MessageOptions.experimental_java_builder_interface', index=1,
+      number=5, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='experimental_java_interface_extends', full_name='proto2.MessageOptions.experimental_java_interface_extends', index=2,
+      number=6, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='message_set_wire_format', full_name='proto2.MessageOptions.message_set_wire_format', index=3,
       number=1, type=8, cpp_type=7, label=1,
       has_default_value=True, default_value=False,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
     _descriptor.FieldDescriptor(
-      name='no_standard_descriptor_accessor', full_name='proto2.MessageOptions.no_standard_descriptor_accessor', index=1,
+      name='no_standard_descriptor_accessor', full_name='proto2.MessageOptions.no_standard_descriptor_accessor', index=4,
       number=2, type=8, cpp_type=7, label=1,
       has_default_value=True, default_value=False,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
     _descriptor.FieldDescriptor(
-      name='deprecated', full_name='proto2.MessageOptions.deprecated', index=2,
+      name='deprecated', full_name='proto2.MessageOptions.deprecated', index=5,
       number=3, type=8, cpp_type=7, label=1,
       has_default_value=True, default_value=False,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
     _descriptor.FieldDescriptor(
-      name='uninterpreted_option', full_name='proto2.MessageOptions.uninterpreted_option', index=3,
+      name='uninterpreted_option', full_name='proto2.MessageOptions.uninterpreted_option', index=6,
       number=999, type=11, cpp_type=10, label=3,
       has_default_value=False, default_value=[],
       message_type=None, enum_type=None, containing_type=None,
@@ -1199,7 +1220,7 @@
   is_extendable=True,
   extension_ranges=[(1000, 536870912), ],
   serialized_start=3509,
-  serialized_end=3711,
+  serialized_end=3846,
 )
 
 
@@ -1233,8 +1254,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=4125,
-  serialized_end=4170,
+  serialized_start=4260,
+  serialized_end=4305,
 )
 
 _FIELDOPTIONS = _descriptor.Descriptor(
@@ -1326,8 +1347,8 @@
   options=None,
   is_extendable=True,
   extension_ranges=[(1000, 536870912), ],
-  serialized_start=3714,
-  serialized_end=4347,
+  serialized_start=3849,
+  serialized_end=4482,
 )
 
 
@@ -1375,8 +1396,8 @@
   options=None,
   is_extendable=True,
   extension_ranges=[(1000, 536870912), ],
-  serialized_start=4350,
-  serialized_end=4503,
+  serialized_start=4485,
+  serialized_end=4638,
 )
 
 
@@ -1410,8 +1431,8 @@
   options=None,
   is_extendable=True,
   extension_ranges=[(1000, 536870912), ],
-  serialized_start=4505,
-  serialized_end=4621,
+  serialized_start=4640,
+  serialized_end=4756,
 )
 
 
@@ -1459,8 +1480,8 @@
   options=None,
   is_extendable=True,
   extension_ranges=[(1000, 536870912), ],
-  serialized_start=4624,
-  serialized_end=4806,
+  serialized_start=4759,
+  serialized_end=4941,
 )
 
 
@@ -1577,14 +1598,28 @@
       is_extension=False, extension_scope=None,
       options=None),
     _descriptor.FieldDescriptor(
-      name='deprecated', full_name='proto2.MethodOptions.deprecated', index=15,
+      name='legacy_client_initial_tokens', full_name='proto2.MethodOptions.legacy_client_initial_tokens', index=15,
+      number=24, type=3, cpp_type=2, label=1,
+      has_default_value=True, default_value=-1,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='legacy_server_initial_tokens', full_name='proto2.MethodOptions.legacy_server_initial_tokens', index=16,
+      number=25, type=3, cpp_type=2, label=1,
+      has_default_value=True, default_value=-1,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+    _descriptor.FieldDescriptor(
+      name='deprecated', full_name='proto2.MethodOptions.deprecated', index=17,
       number=33, type=8, cpp_type=7, label=1,
       has_default_value=True, default_value=False,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
     _descriptor.FieldDescriptor(
-      name='uninterpreted_option', full_name='proto2.MethodOptions.uninterpreted_option', index=16,
+      name='uninterpreted_option', full_name='proto2.MethodOptions.uninterpreted_option', index=18,
       number=999, type=11, cpp_type=10, label=3,
       has_default_value=False, default_value=[],
       message_type=None, enum_type=None, containing_type=None,
@@ -1602,8 +1637,8 @@
   options=None,
   is_extendable=True,
   extension_ranges=[(1000, 536870912), ],
-  serialized_start=4809,
-  serialized_end=5661,
+  serialized_start=4944,
+  serialized_end=5880,
 )
 
 
@@ -1701,8 +1736,8 @@
   options=None,
   is_extendable=True,
   extension_ranges=[(1000, 536870912), ],
-  serialized_start=5664,
-  serialized_end=6142,
+  serialized_start=5883,
+  serialized_end=6361,
 )
 
 
@@ -1736,8 +1771,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=6371,
-  serialized_end=6422,
+  serialized_start=6590,
+  serialized_end=6641,
 )
 
 _UNINTERPRETEDOPTION = _descriptor.Descriptor(
@@ -1805,8 +1840,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=6145,
-  serialized_end=6422,
+  serialized_start=6364,
+  serialized_end=6641,
 )
 
 
@@ -1854,8 +1889,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=6494,
-  serialized_end=6593,
+  serialized_start=6713,
+  serialized_end=6812,
 )
 
 _SOURCECODEINFO = _descriptor.Descriptor(
@@ -1881,8 +1916,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=6425,
-  serialized_end=6593,
+  serialized_start=6644,
+  serialized_end=6812,
 )
 
 _FILEDESCRIPTORSET.fields_by_name['file'].message_type = _FILEDESCRIPTORPROTO
diff --git a/google/net/proto2/python/internal/decoder.py b/google/net/proto2/python/internal/decoder.py
index 5de022d..618d19e 100644
--- a/google/net/proto2/python/internal/decoder.py
+++ b/google/net/proto2/python/internal/decoder.py
@@ -87,7 +87,7 @@
 _DecodeError = message.DecodeError
 
 
-def _VarintDecoder(mask):
+def _VarintDecoder(mask, result_type):
   """Return an encoder for a basic varint value (does not include tag).
 
   Decoded values will be bitwise-anded with the given mask before being
@@ -107,6 +107,7 @@
       pos += 1
       if not (b & 0x80):
         result &= mask
+        result = result_type(result)
         return (result, pos)
       shift += 7
       if shift >= 64:
@@ -114,7 +115,7 @@
   return DecodeVarint
 
 
-def _SignedVarintDecoder(mask):
+def _SignedVarintDecoder(mask, result_type):
   """Like _VarintDecoder() but decodes signed values."""
 
   local_ord = ord
@@ -131,6 +132,7 @@
           result |= ~mask
         else:
           result &= mask
+        result = result_type(result)
         return (result, pos)
       shift += 7
       if shift >= 64:
@@ -138,12 +140,15 @@
   return DecodeVarint
 
 
-_DecodeVarint = _VarintDecoder((1 << 64) - 1)
-_DecodeSignedVarint = _SignedVarintDecoder((1 << 64) - 1)
 
 
-_DecodeVarint32 = _VarintDecoder((1 << 32) - 1)
-_DecodeSignedVarint32 = _SignedVarintDecoder((1 << 32) - 1)
+
+_DecodeVarint = _VarintDecoder((1 << 64) - 1, long)
+_DecodeSignedVarint = _SignedVarintDecoder((1 << 64) - 1, long)
+
+
+_DecodeVarint32 = _VarintDecoder((1 << 32) - 1, int)
+_DecodeSignedVarint32 = _SignedVarintDecoder((1 << 32) - 1, int)
 
 
 def ReadTag(buffer, pos):
diff --git a/google/net/proto2/python/internal/type_checkers.py b/google/net/proto2/python/internal/type_checkers.py
index 1b58ce5..c0330e7 100644
--- a/google/net/proto2/python/internal/type_checkers.py
+++ b/google/net/proto2/python/internal/type_checkers.py
@@ -100,6 +100,10 @@
       raise TypeError(message)
     if not self._MIN <= proposed_value <= self._MAX:
       raise ValueError('Value out of range: %d' % proposed_value)
+
+
+
+    proposed_value = self._TYPE(proposed_value)
     return proposed_value
 
 
@@ -151,21 +155,25 @@
 
   _MIN = -2147483648
   _MAX = 2147483647
+  _TYPE = int
 
 
 class Uint32ValueChecker(IntValueChecker):
   _MIN = 0
   _MAX = (1 << 32) - 1
+  _TYPE = int
 
 
 class Int64ValueChecker(IntValueChecker):
   _MIN = -(1 << 63)
   _MAX = (1 << 63) - 1
+  _TYPE = long
 
 
 class Uint64ValueChecker(IntValueChecker):
   _MIN = 0
   _MAX = (1 << 64) - 1
+  _TYPE = long
 
 
 
diff --git a/google/net/proto2/python/public/descriptor.py b/google/net/proto2/python/public/descriptor.py
index 94dc421..10959e1 100644
--- a/google/net/proto2/python/public/descriptor.py
+++ b/google/net/proto2/python/public/descriptor.py
@@ -652,7 +652,7 @@
   serialized_pb: (str) Byte string of serialized
     descriptor_pb2.FileDescriptorProto.
   dependencies: List of other files this file depends on.
-  messages: List of Descriptors for messages in this file.
+  messages_types_by_name: Dict of message names of their descriptors.
   enum_types_by_name: Dict of enum names and their descriptors.
   extensions_by_name: Dict of extension names and their descriptors.
   """
diff --git a/google/net/proto2/python/public/text_format.py b/google/net/proto2/python/public/text_format.py
index 60e15cc..488bd8c 100644
--- a/google/net/proto2/python/public/text_format.py
+++ b/google/net/proto2/python/public/text_format.py
@@ -708,7 +708,13 @@
   """
 
   try:
-    result = int(text, 0)
+
+
+
+    if is_long:
+      result = long(text, 0)
+    else:
+      result = int(text, 0)
   except ValueError:
     raise ValueError('Couldn\'t parse integer: %s' % text)
 
diff --git a/google/storage/speckle/proto/client_pb2.py b/google/storage/speckle/proto/client_pb2.py
index e431d9f..1ec7079 100644
--- a/google/storage/speckle/proto/client_pb2.py
+++ b/google/storage/speckle/proto/client_pb2.py
@@ -2494,6 +2494,13 @@
 DESCRIPTOR.message_types_by_name['BatchProto'] = _BATCHPROTO
 DESCRIPTOR.message_types_by_name['ParameterMetadata'] = _PARAMETERMETADATA
 DESCRIPTOR.message_types_by_name['RpcErrorProto'] = _RPCERRORPROTO
+DESCRIPTOR.enum_types_by_name['TransactionIsolationLevel'] = _TRANSACTIONISOLATIONLEVEL
+DESCRIPTOR.enum_types_by_name['ResultSetType'] = _RESULTSETTYPE
+DESCRIPTOR.enum_types_by_name['ResultSetConcurrency'] = _RESULTSETCONCURRENCY
+DESCRIPTOR.enum_types_by_name['ResultSetHoldability'] = _RESULTSETHOLDABILITY
+DESCRIPTOR.enum_types_by_name['FetchDirection'] = _FETCHDIRECTION
+DESCRIPTOR.enum_types_by_name['MetadataType'] = _METADATATYPE
+DESCRIPTOR.enum_types_by_name['ClientType'] = _CLIENTTYPE
 
 class BindVariableProto(_message.Message):
   __metaclass__ = _reflection.GeneratedProtocolMessageType
diff --git a/google_sql.py b/google_sql.py
index 661f26e..fa956c5 100644
--- a/google_sql.py
+++ b/google_sql.py
@@ -114,6 +114,16 @@
 API_SERVER_EXTRA_PATH_SCRIPTS = 'api_server'
 
 
+
+
+ENDPOINTSCFG_EXTRA_PATHS = [
+  os.path.join(DIR_PATH, 'lib', 'cherrypy'),
+  os.path.join(DIR_PATH, 'lib', 'concurrent'),
+  os.path.join(DIR_PATH, 'lib', 'endpoints-1.0'),
+]
+ENDPOINTSCFG_EXTRA_PATH_SCRIPTS = 'endpointscfg'
+
+
 OAUTH_CLIENT_EXTRA_PATHS = [
   os.path.join(DIR_PATH, 'lib', 'google-api-python-client'),
   os.path.join(DIR_PATH, 'lib', 'httplib2'),
@@ -161,6 +171,8 @@
     extra_extra_paths = GOOGLE_SQL_EXTRA_PATHS
   elif re.match(API_SERVER_EXTRA_PATH_SCRIPTS, script_name):
     extra_extra_paths = API_SERVER_EXTRA_PATHS
+  elif re.match(ENDPOINTSCFG_EXTRA_PATH_SCRIPTS, script_name):
+    extra_extra_paths = ENDPOINTSCFG_EXTRA_PATHS
   else:
     extra_extra_paths = []
   fix_sys_path(extra_extra_paths)
diff --git a/lib/cacerts/urlfetch_cacerts.txt b/lib/cacerts/urlfetch_cacerts.txt
index 6f43cc4..7f9571c 100644
--- a/lib/cacerts/urlfetch_cacerts.txt
+++ b/lib/cacerts/urlfetch_cacerts.txt
@@ -4458,32 +4458,6 @@
 Ahz40hL0vUztVrCic8hPgs4NtMSvDkNwbgjU7LDBxH91GJl2fWjSE2w3UjRW
 -----END CERTIFICATE-----
 
-subject= /C=FR/O=Port Autonome de Marseille/OU=0002 775558489/CN=AC-PAM
-serial=65277031
------BEGIN CERTIFICATE-----
-MIIDvjCCAqagAwIBAgIEZSdwMTANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMC
-RlIxDzANBgNVBAgTBkZyYW5jZTEOMAwGA1UEBxMFUGFyaXMxEDAOBgNVBAoTB1BN
-L1NHRE4xDjAMBgNVBAsTBURDU1NJMQ4wDAYDVQQDEwVJR0MvQTEjMCEGCSqGSIb3
-DQEJARYUaWdjYUBzZ2RuLnBtLmdvdXYuZnIwHhcNMDQwOTA5MTUzMjQ1WhcNMTMw
-OTA4MTUzMjQ0WjBcMQswCQYDVQQGEwJGUjEjMCEGA1UEChMaUG9ydCBBdXRvbm9t
-ZSBkZSBNYXJzZWlsbGUxFzAVBgNVBAsTDjAwMDIgNzc1NTU4NDg5MQ8wDQYDVQQD
-EwZBQy1QQU0wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNmkvzaQj6
-OMi/V+OkX6MMZTgX8x879Z3zNOmIn/xQ4b1qEOFv0ykRuAIodhYxsTjVtC5d+vIp
-mX2gzVTkPWGTNOiyDf7fKUDFPT0cz1pEgtDlRAwv3Q/avc72gfBfAvMHTK5T2YQG
-pLXCbIRvDBwyFPlsPPxwGLp08gB1MWv7TEQJUlzTOyRmLyZ9lQG7n1TPLqNWCuI3
-ItZcdPGj1V+63jQ+36VMYay0y4LcZOUm9KlYc3kxL++qD1UyqhiuzKcrWgN3DNf+
-Rnz2WMqpzWomo6qZeSmYift9dNKKsojmiDOUMkh1kVnKvv4ifcE1ji/Kme1GGyde
-EJ797/iHzcdFAgMBAAGjXjBcMAwGA1UdEwQFMAMBAf8wDAYDVR0PBAUDAwAGADAd
-BgNVHQ4EFgQUkQ9Qq6jJlaswLhxgjDTZ5KfX4+MwHwYDVR0jBBgwFoAUowUvGGBQ
-wokK3SshT/+OTqgwMTYwDQYJKoZIhvcNAQEFBQADggEBAEfDmOKWHn9/SL1fSlSO
-+NJlN8N44+cDwVe0c26QEP68txH4AGKvePtCAEoRBH0qWuqkm33UdnzjisY+U1gU
-W0Dt1CZLNvPGcg93lZnZVTjpKM2OUjaWLgTI+r3MtIwu4KeWuyIZtMHZWEffyTwQ
-EbMLfTOz2GJtDSOXP8ZkE6oWWtlzWg4fFafrK+PuOvmpprXXKSph1HeZUyCa2hky
-7Sv5GP2atJ+4Wm8xRhw4WMd18BvDACsm8lSVEAXClwpbxLmh3O5e4YlVxHOsY9aA
-OLN5/qWr+AoLkZopDwDbbLtUxntICCMR6cw7XBdVd40dpCnLwTX4rWFufpOQdCUF
-cOY=
------END CERTIFICATE-----
-
 subject= /C=US/O=Thawte, Inc./CN=Thawte SSL CA
 serial=07F69339D0E1CE1BDECBFE76D5915201
 -----BEGIN CERTIFICATE-----
diff --git a/lib/endpoints-1.0/endpoints/LICENSE b/lib/endpoints-1.0/endpoints/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/lib/endpoints-1.0/endpoints/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/lib/endpoints-1.0/endpoints/__init__.py b/lib/endpoints-1.0/endpoints/__init__.py
new file mode 100644
index 0000000..8d38414
--- /dev/null
+++ b/lib/endpoints-1.0/endpoints/__init__.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+
+
+"""Apiserving Module."""
+
+
+
+
+
+
+from api_config import api
+from api_config import API_EXPLORER_CLIENT_ID
+from api_config import AUTH_LEVEL
+from api_config import EMAIL_SCOPE
+from api_config import method
+from api_config import ResourceContainer
+from api_exceptions import *
+from apiserving import *
+import message_parser
+from users_id_token import get_current_user
+from users_id_token import InvalidGetUserCall
+from users_id_token import SKIP_CLIENT_ID_CHECK
+
+__version__ = '1.0'
diff --git a/google/appengine/ext/endpoints/api_backend.py b/lib/endpoints-1.0/endpoints/api_backend.py
similarity index 100%
rename from google/appengine/ext/endpoints/api_backend.py
rename to lib/endpoints-1.0/endpoints/api_backend.py
diff --git a/google/appengine/ext/endpoints/api_backend_service.py b/lib/endpoints-1.0/endpoints/api_backend_service.py
similarity index 96%
rename from google/appengine/ext/endpoints/api_backend_service.py
rename to lib/endpoints-1.0/endpoints/api_backend_service.py
index 7846add..9b46357 100644
--- a/google/appengine/ext/endpoints/api_backend_service.py
+++ b/lib/endpoints-1.0/endpoints/api_backend_service.py
@@ -29,12 +29,11 @@
   import simplejson as json
 import logging
 
+from endpoints import api_backend
+from endpoints import api_config
+from endpoints import api_exceptions
 from protorpc import message_types
 
-from google.appengine.ext.endpoints import api_backend
-from google.appengine.ext.endpoints import api_config
-from google.appengine.ext.endpoints import api_exceptions
-
 
 __all__ = [
     'ApiConfigRegistry',
diff --git a/google/appengine/ext/endpoints/api_config.py b/lib/endpoints-1.0/endpoints/api_config.py
similarity index 81%
rename from google/appengine/ext/endpoints/api_config.py
rename to lib/endpoints-1.0/endpoints/api_config.py
index c8ba3f3..64b20c9 100644
--- a/google/appengine/ext/endpoints/api_config.py
+++ b/lib/endpoints-1.0/endpoints/api_config.py
@@ -40,8 +40,11 @@
   import json
 except ImportError:
   import simplejson as json
+import logging
 import re
 
+from endpoints import message_parser
+from endpoints import users_id_token
 from protorpc import message_types
 from protorpc import messages
 from protorpc import remote
@@ -54,9 +57,6 @@
 
   from google.appengine.api import app_identity
 
-from google.appengine.ext.endpoints import message_parser
-from google.appengine.ext.endpoints import users_id_token
-
 
 __all__ = [
     'API_EXPLORER_CLIENT_ID',
@@ -66,9 +66,11 @@
     'ApiFrontEndLimitRule',
     'ApiFrontEndLimits',
     'CacheControl',
+    'ResourceContainer',
     'EMAIL_SCOPE',
     'api',
     'method',
+    'AUTH_LEVEL'
 ]
 
 
@@ -82,10 +84,255 @@
     'examples how to implement a multi-class API.')
 
 
+def _Enum(docstring, *names):
+  """Utility to generate enum classes used by annotations.
+
+  Args:
+    docstring: Docstring for the generated enum class.
+    *names: Enum names.
+
+  Returns:
+    A class that contains enum names as attributes.
+  """
+  enums = dict(zip(names, range(len(names))))
+  reverse = dict((value, key) for key, value in enums.iteritems())
+  enums['reverse_mapping'] = reverse
+  enums['__doc__'] = docstring
+  return type('Enum', (object,), enums)
+
+_AUTH_LEVEL_DOCSTRING = """
+  Define the enums used by the auth_level annotation to specify frontend
+  authentication requirement.
+
+  Frontend authentication is handled by a Google API server prior to the
+  request reaching backends. An early return before hitting the backend can
+  happen if the request does not fulfil the requirement specified by the
+  auth_level.
+
+  Valid values of auth_level and their meanings are:
+
+  AUTH_LEVEL.REQUIRED: Valid authentication credentials are required. Backend
+    will be called only if authentication credentials are present and valid.
+
+  AUTH_LEVEL.OPTIONAL: Authentication is optional. If authentication credentials
+    are supplied they must be valid. Backend will be called if the request
+    contains valid authentication credentials or no authentication credentials.
+
+  AUTH_LEVEL.OPTIONAL_CONTINUE: Authentication is optional and will be attempted
+    if authentication credentials are supplied. Invalid authentication
+    credentials will be removed but the request can always reach backend.
+
+  AUTH_LEVEL.NONE: Frontend authentication will be skipped. If authentication is
+   desired, it will need to be performed by the backend.
+  """
+
+AUTH_LEVEL = _Enum(_AUTH_LEVEL_DOCSTRING, 'REQUIRED', 'OPTIONAL',
+                   'OPTIONAL_CONTINUE', 'NONE')
+
+
 class ApiConfigurationError(Exception):
   """Exception thrown if there's an error in the configuration/annotations."""
 
 
+def _GetFieldAttributes(field):
+  """Decomposes field into the needed arguments to pass to the constructor.
+
+  This can be used to create copies of the field or to compare if two fields
+  are "equal" (since __eq__ is not implemented on messages.Field).
+
+  Args:
+    field: A ProtoRPC message field (potentially to be copied).
+
+  Raises:
+    TypeError: If the field is not an instance of messages.Field.
+
+  Returns:
+    A pair of relevant arguments to be passed to the constructor for the field
+      type. The first element is a list of positional arguments for the
+      constructor and the second is a dictionary of keyword arguments.
+  """
+  if not isinstance(field, messages.Field):
+    raise TypeError('Field %r to be copied not a ProtoRPC field.' % (field,))
+
+  positional_args = []
+  kwargs = {
+      'required': field.required,
+      'repeated': field.repeated,
+      'variant': field.variant,
+      'default': field._Field__default,
+  }
+
+  if isinstance(field, messages.MessageField):
+
+    kwargs.pop('default')
+    if not isinstance(field, message_types.DateTimeField):
+      positional_args.insert(0, field.message_type)
+  elif isinstance(field, messages.EnumField):
+    positional_args.insert(0, field.type)
+
+  return positional_args, kwargs
+
+
+def _CopyField(field, number=None):
+  """Copies a (potentially) owned ProtoRPC field instance into a new copy.
+
+  Args:
+    field: A ProtoRPC message field to be copied.
+    number: An integer for the field to override the number of the field.
+        Defaults to None.
+
+  Raises:
+    TypeError: If the field is not an instance of messages.Field.
+
+  Returns:
+    A copy of the ProtoRPC message field.
+  """
+  positional_args, kwargs = _GetFieldAttributes(field)
+  number = number or field.number
+  positional_args.append(number)
+  return field.__class__(*positional_args, **kwargs)
+
+
+def _CompareFields(field, other_field):
+  """Checks if two ProtoRPC fields are "equal".
+
+  Compares the arguments, rather than the id of the elements (which is
+  the default __eq__ behavior) as well as the class of the fields.
+
+  Args:
+    field: A ProtoRPC message field to be compared.
+    other_field: A ProtoRPC message field to be compared.
+
+  Returns:
+    Boolean indicating whether the fields are equal.
+  """
+  field_attrs = _GetFieldAttributes(field)
+  other_field_attrs = _GetFieldAttributes(other_field)
+  if field_attrs != other_field_attrs:
+    return False
+  return field.__class__ == other_field.__class__
+
+
+class ResourceContainer(object):
+  """Container for a request body resource combined with parameters.
+
+  Used for API methods which may also have path or query parameters in addition
+  to a request body.
+
+  Attributes:
+    body_message_class: A message class to represent a request body.
+    parameters_message_class: A placeholder message class for request
+        parameters.
+  """
+
+  __remote_info_cache = {}
+
+  __combined_message_class = None
+
+  def __init__(self, _body_message_class=message_types.VoidMessage, **kwargs):
+    """Constructor for ResourceContainer.
+
+    Stores a request body message class and attempts to create one from the
+    keyword arguments passed in.
+
+    Args:
+      _body_message_class: A keyword argument to be treated like a positional
+          argument. This will not conflict with the potential names of fields
+          since they can't begin with underscore. We make this a keyword
+          argument since the default VoidMessage is a very common choice given
+          the prevalence of GET methods.
+      **kwargs: Keyword arguments specifying field names (the named arguments)
+          and instances of ProtoRPC fields as the values.
+    """
+    self.body_message_class = _body_message_class
+    self.parameters_message_class = type('ParameterContainer',
+                                         (messages.Message,), kwargs)
+
+  @property
+  def combined_message_class(self):
+    """A ProtoRPC message class with both request and parameters fields.
+
+    Caches the result in a local private variable. Uses _CopyField to create
+    copies of the fields from the existing request and parameters classes since
+    those fields are "owned" by the message classes.
+
+    Raises:
+      TypeError: If a field name is used in both the request message and the
+        parameters but the two fields do not represent the same type.
+
+    Returns:
+      Value of combined message class for this property.
+    """
+    if self.__combined_message_class is not None:
+      return self.__combined_message_class
+
+    fields = {}
+
+
+
+
+
+
+
+    field_number = 1
+    for field in self.body_message_class.all_fields():
+      fields[field.name] = _CopyField(field, number=field_number)
+      field_number += 1
+    for field in self.parameters_message_class.all_fields():
+      if field.name in fields:
+        if not _CompareFields(field, fields[field.name]):
+          raise TypeError('Field %r contained in both parameters and request '
+                          'body, but the fields differ.' % (field.name,))
+        else:
+
+          continue
+      fields[field.name] = _CopyField(field, number=field_number)
+      field_number += 1
+
+    self.__combined_message_class = type('CombinedContainer',
+                                         (messages.Message,), fields)
+    return self.__combined_message_class
+
+  @classmethod
+  def add_to_cache(cls, remote_info, container):
+    """Adds a ResourceContainer to a cache tying it to a protorpc method.
+
+    Args:
+      remote_info: Instance of protorpc.remote._RemoteMethodInfo corresponding
+          to a method.
+      container: An instance of ResourceContainer.
+
+    Raises:
+      TypeError: if the container is not an instance of cls.
+      KeyError: if the remote method has been reference by a container before.
+          This created remote method should never occur because a remote method
+          is created once.
+    """
+    if not isinstance(container, cls):
+      raise TypeError('%r not an instance of %r, could not be added to cache.' %
+                      (container, cls))
+    if remote_info in cls.__remote_info_cache:
+      raise KeyError('Cache has collision but should not.')
+    cls.__remote_info_cache[remote_info] = container
+
+  @classmethod
+  def get_request_message(cls, remote_info):
+    """Gets request message or container from remote info.
+
+    Args:
+      remote_info: Instance of protorpc.remote._RemoteMethodInfo corresponding
+          to a method.
+
+    Returns:
+      Either an instance of the request type from the remote or the
+          ResourceContainer that was cached with the remote method.
+    """
+    if remote_info in cls.__remote_info_cache:
+      return cls.__remote_info_cache[remote_info]
+    else:
+      return remote_info.request_type()
+
+
 def _CheckListType(settings, allowed_type, name, allow_none=True):
   """Verify that settings in list are of the allowed type or raise TypeError.
 
@@ -132,6 +379,13 @@
     raise TypeError('%s type doesn\'t match %s.' % (name, check_type))
 
 
+def _CheckEnum(value, check_type, name):
+  if value is None:
+    return
+  if value not in check_type.reverse_mapping:
+    raise TypeError('%s is not a valid value for %s' % (value, name))
+
+
 
 class _ApiInfo(object):
   """Configurable attributes of an API.
@@ -145,7 +399,7 @@
 
   @util.positional(2)
   def __init__(self, common_info, resource_name=None, path=None, audiences=None,
-               scopes=None, allowed_client_ids=None):
+               scopes=None, allowed_client_ids=None, auth_level=None):
     """Constructor for _ApiInfo.
 
     Args:
@@ -161,12 +415,15 @@
         (Default: None)
       allowed_client_ids: list of strings, Acceptable client IDs for auth.
         (Default: None)
+      auth_level: enum from AUTH_LEVEL, Frontend authentication level.
+        (Default: None)
     """
     _CheckType(resource_name, basestring, 'resource_name')
     _CheckType(path, basestring, 'path')
     _CheckListType(audiences, basestring, 'audiences')
     _CheckListType(scopes, basestring, 'scopes')
     _CheckListType(allowed_client_ids, basestring, 'allowed_client_ids')
+    _CheckEnum(auth_level, AUTH_LEVEL, 'auth_level')
 
     self.__common_info = common_info
     self.__resource_name = resource_name
@@ -174,6 +431,7 @@
     self.__audiences = audiences
     self.__scopes = scopes
     self.__allowed_client_ids = allowed_client_ids
+    self.__auth_level = auth_level
 
   def is_same_api(self, other):
     """Check if this implements the same API as another _ApiInfo instance."""
@@ -224,6 +482,13 @@
     return self.__common_info.allowed_client_ids
 
   @property
+  def auth_level(self):
+    """Enum from AUTH_LEVEL specifying the frontend authentication level."""
+    if self.__auth_level is not None:
+      return self.__auth_level
+    return self.__common_info.auth_level
+
+  @property
   def canonical_name(self):
     """Canonical name for the API."""
     return self.__common_info.canonical_name
@@ -287,7 +552,7 @@
                audiences=None, scopes=None, allowed_client_ids=None,
                canonical_name=None, auth=None, owner_domain=None,
                owner_name=None, package_path=None, frontend_limits=None,
-               title=None, documentation=None):
+               title=None, documentation=None, auth_level=None):
     """Constructor for _ApiDecorator.
 
     Args:
@@ -318,6 +583,7 @@
       documentation: string, a URL where users can find documentation about this
         version of the API. This will be surfaced in the API Explorer and GPE
         plugin to allow users to learn about your service.
+      auth_level: enum from AUTH_LEVEL, Frontend authentication level.
     """
     self.__common_info = self.__ApiCommonInfo(
         name, version, description=description, hostname=hostname,
@@ -326,7 +592,7 @@
         canonical_name=canonical_name, auth=auth, owner_domain=owner_domain,
         owner_name=owner_name, package_path=package_path,
         frontend_limits=frontend_limits, title=title,
-        documentation=documentation)
+        documentation=documentation, auth_level=auth_level)
     self.__classes = []
 
   class __ApiCommonInfo(object):
@@ -350,7 +616,7 @@
                  audiences=None, scopes=None, allowed_client_ids=None,
                  canonical_name=None, auth=None, owner_domain=None,
                  owner_name=None, package_path=None, frontend_limits=None,
-                 title=None, documentation=None):
+                 title=None, documentation=None, auth_level=None):
       """Constructor for _ApiCommonInfo.
 
       Args:
@@ -381,6 +647,7 @@
         documentation: string, a URL where users can find documentation about
           this version of the API. This will be surfaced in the API Explorer and
           GPE plugin to allow users to learn about your service.
+        auth_level: enum from AUTH_LEVEL, Frontend authentication level.
       """
       _CheckType(name, basestring, 'name', allow_none=False)
       _CheckType(version, basestring, 'version', allow_none=False)
@@ -397,6 +664,7 @@
       _CheckType(frontend_limits, ApiFrontEndLimits, 'frontend_limits')
       _CheckType(title, basestring, 'title')
       _CheckType(documentation, basestring, 'documentation')
+      _CheckEnum(auth_level, AUTH_LEVEL, 'auth_level')
 
       if hostname is None:
         hostname = app_identity.get_default_version_hostname()
@@ -406,6 +674,8 @@
         scopes = [EMAIL_SCOPE]
       if allowed_client_ids is None:
         allowed_client_ids = [API_EXPLORER_CLIENT_ID]
+      if auth_level is None:
+        auth_level = AUTH_LEVEL.NONE
 
       self.__name = name
       self.__version = version
@@ -422,6 +692,7 @@
       self.__frontend_limits = frontend_limits
       self.__title = title
       self.__documentation = documentation
+      self.__auth_level = auth_level
 
     @property
     def name(self):
@@ -459,6 +730,11 @@
       return self.__allowed_client_ids
 
     @property
+    def auth_level(self):
+      """Enum from AUTH_LEVEL specifying default frontend auth level."""
+      return self.__auth_level
+
+    @property
     def canonical_name(self):
       """Canonical name for the API."""
       return self.__canonical_name
@@ -510,7 +786,7 @@
     return self.api_class()(service_class)
 
   def api_class(self, resource_name=None, path=None, audiences=None,
-                scopes=None, allowed_client_ids=None):
+                scopes=None, allowed_client_ids=None, auth_level=None):
     """Get a decorator for a class that implements an API.
 
     This can be used for single-class or multi-class implementations.  It's
@@ -527,6 +803,8 @@
         (Default: None)
       allowed_client_ids: list of strings, Acceptable client IDs for auth.
         (Default: None)
+      auth_level: enum from AUTH_LEVEL, Frontend authentication level.
+        (Default: None)
 
     Returns:
       A decorator function to decorate a class that implements an API.
@@ -545,7 +823,7 @@
       api_class.api_info = _ApiInfo(
           self.__common_info, resource_name=resource_name,
           path=path, audiences=audiences, scopes=scopes,
-          allowed_client_ids=allowed_client_ids)
+          allowed_client_ids=allowed_client_ids, auth_level=auth_level)
       return api_class
 
     return apiserving_api_decorator
@@ -696,7 +974,7 @@
 def api(name, version, description=None, hostname=None, audiences=None,
         scopes=None, allowed_client_ids=None, canonical_name=None,
         auth=None, owner_domain=None, owner_name=None, package_path=None,
-        frontend_limits=None, title=None, documentation=None):
+        frontend_limits=None, title=None, documentation=None, auth_level=None):
   """Decorate a ProtoRPC Service class for use by the framework above.
 
   This decorator can be used to specify an API name, version, description, and
@@ -753,6 +1031,7 @@
     documentation: string, a URL where users can find documentation about this
       version of the API. This will be surfaced in the API Explorer and GPE
       plugin to allow users to learn about your service.
+    auth_level: enum from AUTH_LEVEL, frontend authentication level.
 
   Returns:
     Class decorated with api_info attribute, an instance of ApiInfo.
@@ -765,7 +1044,7 @@
                        owner_domain=owner_domain, owner_name=owner_name,
                        package_path=package_path,
                        frontend_limits=frontend_limits, title=title,
-                       documentation=documentation)
+                       documentation=documentation, auth_level=auth_level)
 
 
 class CacheControl(object):
@@ -816,7 +1095,7 @@
   @util.positional(1)
   def __init__(self, name=None, path=None, http_method=None,
                cache_control=None, scopes=None, audiences=None,
-               allowed_client_ids=None):
+               allowed_client_ids=None, auth_level=None):
     """Constructor.
 
     Args:
@@ -828,6 +1107,7 @@
       scopes: list of string, OAuth2 token must contain one of these scopes.
       audiences: list of string, IdToken must contain one of these audiences.
       allowed_client_ids: list of string, Client IDs allowed to call the method.
+      auth_level: enum from AUTH_LEVEL, Frontend auth level for the method.
     """
     self.__name = name
     self.__path = path
@@ -836,6 +1116,7 @@
     self.__scopes = scopes
     self.__audiences = audiences
     self.__allowed_client_ids = allowed_client_ids
+    self.__auth_level = auth_level
 
   def __safe_name(self, method_name):
     """Restrict method name to a-zA-Z0-9, first char lowercase."""
@@ -909,6 +1190,11 @@
     """List of allowed client IDs for the API method."""
     return self.__allowed_client_ids
 
+  @property
+  def auth_level(self):
+    """Enum from AUTH_LEVEL specifying default frontend auth level."""
+    return self.__auth_level
+
   def method_id(self, api_info):
     """Computed method name."""
 
@@ -931,11 +1217,12 @@
            cache_control=None,
            scopes=None,
            audiences=None,
-           allowed_client_ids=None):
+           allowed_client_ids=None,
+           auth_level=None):
   """Decorate a ProtoRPC Method for use by the framework above.
 
   This decorator can be used to specify a method name, path, http method,
-  cache control, scopes, audiences, and client ids
+  cache control, scopes, audiences, client ids and auth_level.
 
   Sample usage:
     @api_config.method(RequestMessage, ResponseMessage,
@@ -956,6 +1243,7 @@
     audiences: list of string, IdToken must contain one of these audiences.
     allowed_client_ids: list of string, Client IDs allowed to call the method.
       Currently limited to 5.  If None, no calls will be allowed.
+    auth_level: enum from AUTH_LEVEL, Frontend auth level for the method.
 
   Returns:
     'apiserving_method_wrapper' function.
@@ -1007,8 +1295,15 @@
     Raises:
       TypeError: if the request_type or response_type parameters are not
         proper subclasses of messages.Message.
+      KeyError: if the request_message is a ResourceContainer and the newly
+          created remote method has been reference by the container before. This
+          should never occur because a remote method is created once.
     """
-    remote_decorator = remote.method(request_message, response_message)
+    if isinstance(request_message, ResourceContainer):
+      remote_decorator = remote.method(request_message.combined_message_class,
+                                       response_message)
+    else:
+      remote_decorator = remote.method(request_message, response_message)
     remote_method = remote_decorator(api_method)
 
     def invoke_remote(service_instance, request):
@@ -1021,11 +1316,14 @@
       return remote_method(service_instance, request)
 
     invoke_remote.remote = remote_method.remote
+    if isinstance(request_message, ResourceContainer):
+      ResourceContainer.add_to_cache(invoke_remote.remote, request_message)
+
     invoke_remote.method_info = _MethodInfo(
         name=name or api_method.__name__, path=path or '',
         http_method=http_method or DEFAULT_HTTP_METHOD,
         cache_control=cache_control, scopes=scopes, audiences=audiences,
-        allowed_client_ids=allowed_client_ids)
+        allowed_client_ids=allowed_client_ids, auth_level=auth_level)
     invoke_remote.__name__ = invoke_remote.method_info.name
     return invoke_remote
 
@@ -1033,6 +1331,7 @@
   _CheckListType(scopes, basestring, 'scopes')
   _CheckListType(audiences, basestring, 'audiences')
   _CheckListType(allowed_client_ids, basestring, 'allowed_client_ids')
+  _CheckEnum(auth_level, AUTH_LEVEL, 'auth_level')
   if allowed_client_ids is not None and len(allowed_client_ids) > 5:
     raise ValueError('allowed_client_ids must have 5 or fewer entries.')
   return apiserving_method_decorator
@@ -1407,8 +1706,15 @@
 
       params[qualified_name] = descriptor
 
-  def __params_descriptor(self, message_type, request_kind, path):
-    """Describe the parameters of a method.
+  def __params_descriptor_without_container(self, message_type,
+                                            request_kind, path):
+    """Describe parameters of a method which does not use a ResourceContainer.
+
+    Makes sure that the path parameters are included in the message definition
+    and adds any required fields and URL query parameters.
+
+    This method is to preserve backwards compatibility and will be removed in
+    a future release.
 
     Args:
       message_type: messages.Message class, Message with parameters to describe.
@@ -1432,13 +1738,67 @@
 
     return params, param_order
 
+
+
+
+  def __params_descriptor(self, message_type, request_kind, path):
+    """Describe the parameters of a method.
+
+    If the message_type is not a ResourceContainer, will fall back to
+    __params_descriptor_without_container (which will eventually be deprecated).
+
+    If the message type is a ResourceContainer, then all path/query parameters
+    will come from the ResourceContainer. This method will also make sure all
+    path parameters are covered by the message fields.
+
+    Args:
+      message_type: messages.Message or ResourceContainer class, Message with
+        parameters to describe.
+      request_kind: The type of request being made.
+      path: string, HTTP path to method.
+
+    Returns:
+      A tuple (dict, list of string): Descriptor of the parameters, Order of the
+        parameters.
+    """
+    path_parameter_dict = self.__get_path_parameters(path)
+
+    if not isinstance(message_type, ResourceContainer):
+      if path_parameter_dict:
+        logging.warning('Method specifies path parameters but you are not '
+                        'using a ResourceContainer. This will fail in future '
+                        'releases; please switch to using ResourceContainer as '
+                        'soon as possible.')
+      return self.__params_descriptor_without_container(
+          message_type, request_kind, path)
+
+
+    message_type = message_type.parameters_message_class()
+
+    params = {}
+    param_order = []
+
+
+    for field_name, matched_path_parameters in path_parameter_dict.iteritems():
+      field = message_type.field_by_name(field_name)
+      self.__validate_path_parameters(field, matched_path_parameters)
+
+
+    for field in sorted(message_type.all_fields(), key=lambda f: f.number):
+      matched_path_parameters = path_parameter_dict.get(field.name, [])
+      self.__add_parameters_from_field(field, matched_path_parameters,
+                                       params, param_order)
+
+    return params, param_order
+
   def __request_message_descriptor(self, request_kind, message_type, method_id,
                                    path):
     """Describes the parameters and body of the request.
 
     Args:
       request_kind: The type of request being made.
-      message_type: messages.Message class, The message to describe.
+      message_type: messages.Message or ResourceContainer class. The message to
+          describe.
       method_id: string, Unique method identifier (e.g. 'myapi.items.method')
       path: string, HTTP path to method.
 
@@ -1450,8 +1810,11 @@
     """
     descriptor = {}
 
-    params, param_order = self.__params_descriptor(message_type, request_kind,
-                                                   path)
+    params, param_order = self.__params_descriptor(message_type,
+                                                   request_kind, path)
+
+    if isinstance(message_type, ResourceContainer):
+      message_type = message_type.body_message_class()
 
     if (request_kind == self.__NO_BODY or
         message_type == message_types.VoidMessage()):
@@ -1519,7 +1882,8 @@
     """
     descriptor = {}
 
-    request_message_type = protorpc_method_info.remote.request_type()
+    request_message_type = ResourceContainer.get_request_message(
+        protorpc_method_info.remote)
     request_kind = self.__get_request_kind(method_info)
     remote_method = protorpc_method_info.remote
 
@@ -1556,6 +1920,12 @@
     if remote_method.method.__doc__:
       descriptor['description'] = remote_method.method.__doc__
 
+    auth_level = (method_info.auth_level
+                  if method_info.auth_level is not None
+                  else service.api_info.auth_level)
+    if auth_level:
+      descriptor['authLevel'] = AUTH_LEVEL.reverse_mapping[auth_level]
+
     return descriptor
 
   def __schema_descriptor(self, services):
diff --git a/google/appengine/ext/endpoints/api_exceptions.py b/lib/endpoints-1.0/endpoints/api_exceptions.py
similarity index 100%
rename from google/appengine/ext/endpoints/api_exceptions.py
rename to lib/endpoints-1.0/endpoints/api_exceptions.py
diff --git a/google/appengine/ext/endpoints/apiserving.py b/lib/endpoints-1.0/endpoints/apiserving.py
similarity index 97%
rename from google/appengine/ext/endpoints/apiserving.py
rename to lib/endpoints-1.0/endpoints/apiserving.py
index 0ab4fe7..a12648a 100644
--- a/google/appengine/ext/endpoints/apiserving.py
+++ b/lib/endpoints-1.0/endpoints/apiserving.py
@@ -64,18 +64,16 @@
 import cgi
 import cStringIO
 import httplib
-import logging
 import os
 
+from endpoints import api_backend_service
+from endpoints import api_config
+from endpoints import api_exceptions
+from endpoints import protojson
 from protorpc import messages
 from protorpc import remote
 from protorpc.wsgi import service as wsgi_service
 
-from google.appengine.ext.endpoints import api_backend_service
-from google.appengine.ext.endpoints import api_config
-from google.appengine.ext.endpoints import api_exceptions
-from google.appengine.ext.endpoints import protojson
-
 package = 'google.appengine.endpoints'
 
 
@@ -180,8 +178,11 @@
   __SPI_PREFIX = '/_ah/spi/'
   __BACKEND_SERVICE_ROOT = '%sBackendService' % __SPI_PREFIX
   __SERVER_SOFTWARE = 'SERVER_SOFTWARE'
-  __DEV_APPSERVER_PREFIX = 'Development/'
-  __TEST_APPSERVER_PREFIX = 'WSGIServer/'
+
+
+
+
+  __IGNORE_RESTRICTION_PREFIXES = ('Development/', 'WSGIServer/', 'testutil/')
   __HEADER_NAME_PEER = 'HTTP_X_APPENGINE_PEER'
   __GOOGLE_PEER = 'apiserving'
 
@@ -339,9 +340,9 @@
     if not self.restricted:
       return False
     server = environ.get(self.__SERVER_SOFTWARE, '')
-    if (server.startswith(self.__DEV_APPSERVER_PREFIX) or
-        server.startswith(self.__TEST_APPSERVER_PREFIX)):
-      return False
+    for prefix in self.__IGNORE_RESTRICTION_PREFIXES:
+      if server.startswith(prefix):
+        return False
     peer_name = environ.get(self.__HEADER_NAME_PEER, '')
     return peer_name.lower() != self.__GOOGLE_PEER
 
diff --git a/google/appengine/ext/endpoints/message_parser.py b/lib/endpoints-1.0/endpoints/message_parser.py
similarity index 99%
rename from google/appengine/ext/endpoints/message_parser.py
rename to lib/endpoints-1.0/endpoints/message_parser.py
index b24de99..0d7a99f 100644
--- a/google/appengine/ext/endpoints/message_parser.py
+++ b/lib/endpoints-1.0/endpoints/message_parser.py
@@ -25,8 +25,6 @@
 
 import re
 
-import google
-
 from protorpc import message_types
 from protorpc import messages
 
diff --git a/google/appengine/ext/endpoints/protojson.py b/lib/endpoints-1.0/endpoints/protojson.py
similarity index 100%
rename from google/appengine/ext/endpoints/protojson.py
rename to lib/endpoints-1.0/endpoints/protojson.py
diff --git a/google/appengine/ext/endpoints/users_id_token.py b/lib/endpoints-1.0/endpoints/users_id_token.py
similarity index 99%
rename from google/appengine/ext/endpoints/users_id_token.py
rename to lib/endpoints-1.0/endpoints/users_id_token.py
index 3afe827..eb4e309 100644
--- a/google/appengine/ext/endpoints/users_id_token.py
+++ b/lib/endpoints-1.0/endpoints/users_id_token.py
@@ -36,8 +36,6 @@
 import time
 import urllib
 
-import google
-
 try:
 
   from google.appengine.api import memcache
diff --git a/old_dev_appserver.py b/old_dev_appserver.py
index 661f26e..fa956c5 100644
--- a/old_dev_appserver.py
+++ b/old_dev_appserver.py
@@ -114,6 +114,16 @@
 API_SERVER_EXTRA_PATH_SCRIPTS = 'api_server'
 
 
+
+
+ENDPOINTSCFG_EXTRA_PATHS = [
+  os.path.join(DIR_PATH, 'lib', 'cherrypy'),
+  os.path.join(DIR_PATH, 'lib', 'concurrent'),
+  os.path.join(DIR_PATH, 'lib', 'endpoints-1.0'),
+]
+ENDPOINTSCFG_EXTRA_PATH_SCRIPTS = 'endpointscfg'
+
+
 OAUTH_CLIENT_EXTRA_PATHS = [
   os.path.join(DIR_PATH, 'lib', 'google-api-python-client'),
   os.path.join(DIR_PATH, 'lib', 'httplib2'),
@@ -161,6 +171,8 @@
     extra_extra_paths = GOOGLE_SQL_EXTRA_PATHS
   elif re.match(API_SERVER_EXTRA_PATH_SCRIPTS, script_name):
     extra_extra_paths = API_SERVER_EXTRA_PATHS
+  elif re.match(ENDPOINTSCFG_EXTRA_PATH_SCRIPTS, script_name):
+    extra_extra_paths = ENDPOINTSCFG_EXTRA_PATHS
   else:
     extra_extra_paths = []
   fix_sys_path(extra_extra_paths)
diff --git a/php/sdk/google/appengine/api/app_identity/AppIdentityService.php b/php/sdk/google/appengine/api/app_identity/AppIdentityService.php
index 607be38..bf171c1 100644
--- a/php/sdk/google/appengine/api/app_identity/AppIdentityService.php
+++ b/php/sdk/google/appengine/api/app_identity/AppIdentityService.php
@@ -19,17 +19,17 @@
 
 namespace google\appengine\api\app_identity;
 
-use \google\appengine\AppIdentityServiceError\ErrorCode;
-use \google\appengine\GetAccessTokenRequest;
-use \google\appengine\GetAccessTokenResponse;
-use \google\appengine\GetPublicCertificateForAppRequest;
-use \google\appengine\GetPublicCertificateForAppResponse;
-use \google\appengine\GetServiceAccountNameRequest;
-use \google\appengine\GetServiceAccountNameResponse;
-use \google\appengine\SignForAppRequest;
-use \google\appengine\SignForAppResponse;
-use \google\appengine\runtime\ApiProxy;
-use \google\appengine\runtime\ApplicationError;
+use google\appengine\AppIdentityServiceError\ErrorCode;
+use google\appengine\GetAccessTokenRequest;
+use google\appengine\GetAccessTokenResponse;
+use google\appengine\GetPublicCertificateForAppRequest;
+use google\appengine\GetPublicCertificateForAppResponse;
+use google\appengine\GetServiceAccountNameRequest;
+use google\appengine\GetServiceAccountNameResponse;
+use google\appengine\SignForAppRequest;
+use google\appengine\SignForAppResponse;
+use google\appengine\runtime\ApiProxy;
+use google\appengine\runtime\ApplicationError;
 
 require_once 'google/appengine/api/app_identity/app_identity_service_pb.php';
 require_once 'google/appengine/api/app_identity/AppIdentityException.php';
@@ -181,7 +181,7 @@
     $memcache = new \Memcache();
     $result = $memcache->get($memcache_key);
 
-    if ($result === False) {
+    if ($result === false) {
       $result = self::getAccessTokenUncached($scopes);
 
       // Cache in memcache allowing for 5 minute clock skew.
diff --git a/php/sdk/google/appengine/api/app_identity/AppIdentityServiceTest.php b/php/sdk/google/appengine/api/app_identity/AppIdentityServiceTest.php
index 3c470f2..11e17c7 100644
--- a/php/sdk/google/appengine/api/app_identity/AppIdentityServiceTest.php
+++ b/php/sdk/google/appengine/api/app_identity/AppIdentityServiceTest.php
@@ -24,9 +24,9 @@
 require_once 'google/appengine/runtime/Memcache.php';
 require_once 'google/appengine/testing/ApiProxyTestBase.php';
 
-use \google\appengine\AppIdentityServiceError\ErrorCode;
-use \google\appengine\api\app_identity\AppIdentityService;
-use \google\appengine\testing\ApiProxyTestBase;
+use google\appengine\AppIdentityServiceError\ErrorCode;
+use google\appengine\api\app_identity\AppIdentityService;
+use google\appengine\testing\ApiProxyTestBase;
 
 /**
  * Unittest for AppIdentityService class.
@@ -135,7 +135,9 @@
                                     'Get',
                                     $req,
                                     $resp);
-    if ($cached) return;
+    if ($cached) {
+      return;
+    }
     $req = new \google\appengine\GetAccessTokenRequest();
     foreach ($scopes as $scope) {
       $req->addScope($scope);
@@ -154,7 +156,9 @@
                                     $req,
                                     $resp);
 
-    if (!is_null($exception)) return;
+    if (!is_null($exception)) {
+      return;
+    }
 
     $req = new \google\appengine\MemcacheSetRequest();
     $item = $req->addItem();
diff --git a/php/sdk/google/appengine/api/cloud_storage/CloudStorageTools.php b/php/sdk/google/appengine/api/cloud_storage/CloudStorageTools.php
index 212b725..f7f7049 100644
--- a/php/sdk/google/appengine/api/cloud_storage/CloudStorageTools.php
+++ b/php/sdk/google/appengine/api/cloud_storage/CloudStorageTools.php
@@ -18,21 +18,21 @@
  */
 namespace google\appengine\api\cloud_storage;
 
-use \google\appengine\BlobstoreServiceError\ErrorCode;
-use \google\appengine\CreateEncodedGoogleStorageKeyRequest;
-use \google\appengine\CreateEncodedGoogleStorageKeyResponse;
-use \google\appengine\CreateUploadURLRequest;
-use \google\appengine\CreateUploadURLResponse;
-use \google\appengine\ImagesDeleteUrlBaseRequest;
-use \google\appengine\ImagesDeleteUrlBaseResponse;
-use \google\appengine\ImagesGetUrlBaseRequest;
-use \google\appengine\ImagesGetUrlBaseResponse;
-use \google\appengine\ImagesServiceError;
-use \google\appengine\files\GetDefaultGsBucketNameRequest;
-use \google\appengine\files\GetDefaultGsBucketNameResponse;
-use \google\appengine\runtime\ApiProxy;
-use \google\appengine\runtime\ApplicationError;
-use \google\appengine\util as util;
+use google\appengine\BlobstoreServiceError\ErrorCode;
+use google\appengine\CreateEncodedGoogleStorageKeyRequest;
+use google\appengine\CreateEncodedGoogleStorageKeyResponse;
+use google\appengine\CreateUploadURLRequest;
+use google\appengine\CreateUploadURLResponse;
+use google\appengine\ImagesDeleteUrlBaseRequest;
+use google\appengine\ImagesDeleteUrlBaseResponse;
+use google\appengine\ImagesGetUrlBaseRequest;
+use google\appengine\ImagesGetUrlBaseResponse;
+use google\appengine\ImagesServiceError;
+use google\appengine\files\GetDefaultGsBucketNameRequest;
+use google\appengine\files\GetDefaultGsBucketNameResponse;
+use google\appengine\runtime\ApiProxy;
+use google\appengine\runtime\ApplicationError;
+use google\appengine\util as util;
 
 require_once 'google/appengine/api/blobstore/blobstore_service_pb.php';
 require_once 'google/appengine/api/cloud_storage/CloudStorageException.php';
@@ -71,7 +71,7 @@
   private static $get_image_serving_url_default_options = [
     'crop'       => false,
     'secure_url' => false,
-    'size'       => NULL
+    'size'       => null,
   ];
 
   /**
@@ -376,17 +376,17 @@
     }
 
     // Determine the range to send
-    $start = util\FindByKeyOrNull($options, "start");
-    $end = util\FindByKeyOrNull($options, "end");
-    $use_range = util\FindByKeyOrNull($options, "use_range");
-    $request_range_header = util\FindByKeyOrNull($_SERVER, "HTTP_RANGE");
+    $start = util\findByKeyOrNull($options, "start");
+    $end = util\findByKeyOrNull($options, "end");
+    $use_range = util\findByKeyOrNull($options, "use_range");
+    $request_range_header = util\findByKeyOrNull($_SERVER, "HTTP_RANGE");
 
     $range_header = self::checkRanges($start,
                                       $end,
                                       $use_range,
                                       $request_range_header);
 
-    $save_as = util\FindByKeyOrNull($options, "save_as");
+    $save_as = util\findByKeyOrNull($options, "save_as");
     if (isset($save_as) && !is_string($save_as)) {
       throw new \InvalidArgumentException("Unexpected value for save_as.");
     }
@@ -398,7 +398,7 @@
       self::sendHeader(self::BLOB_RANGE_HEADER, $range_header);
     }
 
-    $content_type = util\FindByKeyOrNull($options, "content_type");
+    $content_type = util\findByKeyOrNull($options, "content_type");
     if (isset($content_type)) {
       self::sendHeader("Content-Type", $content_type);
     }
diff --git a/php/sdk/google/appengine/api/log/AppLogLine.php b/php/sdk/google/appengine/api/log/AppLogLine.php
new file mode 100644
index 0000000..00d5fc7
--- /dev/null
+++ b/php/sdk/google/appengine/api/log/AppLogLine.php
@@ -0,0 +1,73 @@
+<?php
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ */
+
+namespace google\appengine\api\log;
+
+/**
+ * Contains the details of a single application log created by calling
+ * @link http://php.net/manual/en/function.syslog.php syslog within the context
+ * of a request.
+ */
+final class AppLogLine {
+  private $pb;
+
+  /**
+   * @param LogLine $pb The underlying protocol buffer.
+   *
+   * @internal
+   */
+  public function __construct(\google\appengine\LogLine $pb) {
+    $this->pb = $pb;
+  }
+
+  /**
+   * The severity of the log message. This differs from the level passed to
+   * <code>syslog</code> as described in @link LogService.
+   *
+   * @return integer The log severity.
+   */
+  public function getLevel() {
+    return $this->pb->getLevel();
+  }
+
+  /**
+   * @return string The message logged by the application.
+   */
+  public function getMessage() {
+    return $this->pb->getLogMessage();
+  }
+
+  /**
+   * @return double The time the log was created in microseconds since the
+   * Unix epoch.
+   */
+  public function getTimeUsec() {
+    return (double) $this->pb->getTime();
+  }
+
+  /**
+   * Returns The same value as {@link getTimeUsec()} as a DateTime.
+   * @return DateTime The time the log was created accurate to the second.
+   */
+  public function getDateTime() {
+    $result = new \DateTime();
+    $result->setTimestamp((double) $this->pb->getTime() / 1e6);
+    return $result;
+  }
+}
diff --git a/php/sdk/google/appengine/api/log/LogException.php b/php/sdk/google/appengine/api/log/LogException.php
new file mode 100644
index 0000000..e48dea2
--- /dev/null
+++ b/php/sdk/google/appengine/api/log/LogException.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ */
+
+namespace google\appengine\api\log;
+
+/**
+ * Thrown when there is a failure using the LogService.
+ */
+final class LogException extends \Exception {
+}
diff --git a/php/sdk/google/appengine/api/log/LogService.php b/php/sdk/google/appengine/api/log/LogService.php
new file mode 100644
index 0000000..8d47e37
--- /dev/null
+++ b/php/sdk/google/appengine/api/log/LogService.php
@@ -0,0 +1,420 @@
+<?php
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ */
+
+namespace google\appengine\api\log;
+
+require_once 'google/appengine/api/logservice/log_service_pb.php';
+require_once 'google/appengine/api/log/RequestLogIterator.php';
+require_once 'google/appengine/api/log/LogException.php';
+require_once 'google/appengine/runtime/ApiProxy.php';
+require_once 'google/appengine/util/string_util.php';
+
+use google\appengine\LogReadRequest;
+use google\appengine\LogReadResponse;
+use google\appengine\LogServiceError\ErrorCode;
+use google\appengine\runtime\ApiProxy;
+use google\appengine\runtime\ApplicationError;
+use google\appengine\util as util;
+
+/**
+ * The LogService allows an application to query for request and application
+ * logs.  Application logs are added to a the current request log by calling
+ * @link http://php.net/manual/en/function.syslog.php syslog(int $priority,
+ * string $message). The $priority used when creating the application log is
+ * translated into a different scale of severity used by the LogService.
+ *
+ * Application logs have a level in order of increasing severity:
+ * <table>
+ *   <tr><th>syslog $priority</th><th>GAE severity</th></tr>
+ *   <tr><td>LOG_DEBUG</td><td>LogService::LEVEL_DEBUG</td></tr>
+ *   <tr><td>LOG_INFO</td><td>LogService::LEVEL_INFO</td></tr>
+ *   <tr><td>LOG_NOTICE</td><td>LogService::LEVEL_INFO</td></tr>
+ *   <tr><td>LOG_WARNING</td><td>LogService::LEVEL_WARNING</td></tr>
+ *   <tr><td>LOG_ERR</td><td>LogService::LEVEL_ERROR</td></tr>
+ *   <tr><td>LOG_CRIT</td><td>LogService::LEVEL_CRITICAL</td></tr>
+ *   <tr><td>LOG_ALERT</td><td>LogService::LEVEL_CRITICAL</td></tr>
+ *   <tr><td>LOG_EMERG</td><td>LogService::LEVEL_CRITICAL</td></tr>
+ * <table>
+ *
+ * When fetching application logs or filtering request logs by severity use the
+ * values in the right hand column.
+ */
+final class LogService {
+   use ApiProxyAccess;
+
+   // Map syslog priority levels to appengine severity levels.
+   private static $syslog_priority_map = array(
+       LOG_EMERG => self::LEVEL_CRITICAL,
+       LOG_ALERT => self::LEVEL_CRITICAL,
+       LOG_CRIT => self::LEVEL_CRITICAL,
+       LOG_ERR => self::LEVEL_ERROR,
+       LOG_WARNING => self::LEVEL_WARNING,
+       LOG_NOTICE => self::LEVEL_INFO,
+       LOG_INFO => self::LEVEL_INFO,
+       LOG_DEBUG => self::LEVEL_DEBUG);
+
+  /**
+   * Constants for application log levels.
+   */
+  const LEVEL_DEBUG = 0;
+  const LEVEL_INFO = 1;
+  const LEVEL_WARNING = 2;
+  const LEVEL_ERROR = 3;
+  const LEVEL_CRITICAL = 4;
+
+  /**
+   * The maximum number of request logs returned in each batch.
+   */
+  const MAX_BATCH_SIZE = 1000;
+
+  # Validation patterns copied from google/appengine/api/logservice/logservice.py
+  private static $MAJOR_VERSION_ID_REGEX =
+      '/^(?:(?:((?!-)[a-z\d\-]{1,63}):)?)((?!-)[a-z\d\-]{1,100})$/';
+  private static $REQUEST_ID_REGEX = '/^[\da-fA-F]+$/';
+
+  /**
+   * Get request logs matching the given options in reverse chronological
+   * order of request end time.
+   *
+   * @param array $options Optional associateive arrary of filters and
+   * modifiers from following:
+   *
+   * <ul>
+   *   <li>'start_time': <code>DateTime or numeric</code> The earliest
+   *   completion time or last-update time for request logs. If the value is
+   *   numeric it represents microseconds since Unix epoch.</li>
+   *   <li>'end_time': <code>DateTime or numeric</code> The latest completion
+   *   time or last-update time for request logs. If the value is numeric it
+   *   represents microseconds since Unix epoch.</li>
+   *   <li>'offset': <code>string</code> The url-safe offset value from a
+   *   <code>RequestLog</code> to continue iterating after.</li>
+   *   <li>'minimum_log_level': <code>integer</code> Only return request logs
+   *   containing at least one application log of this severity or higher.
+   *   Works even if include_app_logs is not <code>true</code></li>
+   *   <li>'include_incomplete': <code>boolean</code> Should incomplete request
+   *   logs be included. The default is <code>false</code> - only completed
+   *   logs are returned</li>
+   *   <li>'include_app_logs': <code>boolean</code> Should application logs be
+   *   returned. The default is <code>false</code> - application logs are not
+   *   returned with their containing request logs.</li>
+   *   <li>'versions': <code>array</code> The versions of the default module
+   *   for which to fetch request logs. Only one of 'versions' and
+   *   'module_versions' can be used.</li>
+   *   <li>'module_versions': <code>arrary/code> An associative array of module
+   *   names to versions for which to fetch request logs.  Each module name may
+   *   be mapped to either a single <code>string</code> version or an <code>
+   *   array</code> of versions.</li>
+   *   <li>'batch_size': <code>integer</code> The number of request logs to
+   *   pre-fetch while iterating.</li>
+   * </ul>
+   *
+   * @return Iterator The matching <code>RequestLog</code> items.
+   */
+  public static function fetch(array $options = []) {
+    $request = new LogReadRequest();
+    $request->setAppId(getenv('APPLICATION_ID'));
+
+    // Required options default values - overridden by options below.
+    $batch_size = 20;
+    $include_app_logs = false;
+    $include_incomplete = false;
+
+    foreach ($options as $key => $value) {
+      switch ($key) {
+        case 'start_time':
+          if (is_numeric($value)) {
+            $usec = (double) $value;
+          } else if ($value instanceof \DateTime) {
+            $usec = self::dateTimeToUsecs($value);
+          } else {
+            self::optionTypeException($key, $value, 'DateTime or numeric');
+          }
+          $request->setStartTime($usec);
+          break;
+        case 'end_time':
+          if (is_numeric($value)) {
+            $usec = (double) $value;
+          } else if ($value instanceof \DateTime) {
+            $usec = self::dateTimeToUsecs($value);
+          } else {
+            self::optionTypeException($key, $value, 'DateTime or numeric');
+          }
+          $request->setEndTime($usec);
+          break;
+        case 'offset':
+          if (!is_string($value)) {
+            self::optionTypeException($key, $value, 'string');
+          }
+          $decoded = util\base64UrlDecode($value);
+          $request->mutableOffset()->parseFromString($decoded);
+          break;
+        case 'minimum_log_level':
+          if (!is_int($value)) {
+            self::optionTypeException($key, $value, 'integer');
+          }
+          if ($value > self::LEVEL_CRITICAL ||
+              $value < self::LEVEL_DEBUG) {
+            throw new \InvalidArgumentException(
+                "Option 'minimum_log_level' must be from " .
+                self::LEVEL_DEBUG . " to " .
+                self::LEVEL_CRITICAL);
+          }
+          $request->setMinimumLogLevel($value);
+          break;
+        case 'include_incomplete':
+          if (!is_bool($value)) {
+            self::optionTypeException($key, $value, 'boolean');
+          }
+          $include_incomplete = $value;
+          break;
+        case 'include_app_logs':
+          if (!is_bool($value)) {
+            self::optionTypeException($key, $value, 'boolean');
+          }
+          $include_app_logs = $value;
+          break;
+        case 'module_versions':
+          if (!is_array($value)) {
+            self::optionTypeException($key, $value, 'array');
+          }
+          if (isset($options['versions'])) {
+              throw new \InvalidArgumentException(
+                  "Only one of 'versions' or " .
+                  "'module_versions' may be set");
+          }
+          foreach ($value as $module => $versions) {
+            if (!is_string($module)) {
+              throw new \InvalidArgumentException(
+                  'Server must be a string but was ' .
+                  self::typeOrClass($module));
+            }
+            // Versions can be a single string or an array of strings.
+            if (is_array($versions)) {
+              foreach ($versions as $version) {
+                if (!is_string($version)) {
+                  throw new \InvalidArgumentException(
+                      'Server version must be a string but was ' .
+                      self::typeOrClass($version));
+                }
+                $module_version = $request->addModuleVersion();
+                if ($module !== 'default') {
+                  $module_version->setModuleId($module);
+                }
+                $module_version->setVersionId($version);
+              }
+            } else if (is_string($versions)) {
+              $module_version = $request->addModuleVersion();
+              $module_version->setModuleId($module);
+              $module_version->setVersionId($versions);
+            } else {
+              throw new \InvalidArgumentException(
+                  'Server version must be a string or array but was ' .
+                  self::typeOrClass($versions));
+            }
+          }
+          break;
+        case 'versions':
+          if (!is_array($value)) {
+            self::optionTypeException($key, $value, 'array');
+          }
+          if (isset($options['module_versions'])) {
+              throw new \InvalidArgumentException(
+                  "Only one of 'versions' or " .
+                  "'module_versions' may be set");
+          }
+          foreach ($value as $version) {
+            if (!is_string($version)) {
+              throw new \InvalidArgumentException(
+                  'Version must be a string but was ' .
+                  self::typeOrClass($version));
+            }
+            if (!preg_match(self::$MAJOR_VERSION_ID_REGEX, $version)) {
+              throw new \InvalidArgumentException(
+                  "Invalid version id $version");
+            }
+            $request->addModuleVersion()->setVersionId($version);
+          }
+          break;
+        case 'batch_size':
+          if (!is_int($value)) {
+            self::optionTypeException($key, $value, 'integer');
+          }
+          if ($value > self::MAX_BATCH_SIZE || $value < 1) {
+            throw new \InvalidArgumentException(
+                'Batch size must be > 0 and <= ' . self::MAX_BATCH_SIZE);
+          }
+          $batch_size = $value;
+          break;
+        default:
+          throw new \InvalidArgumentException("Invalid option $key");
+      }
+    }
+
+    // Set required options.
+    $request->setIncludeIncomplete($include_incomplete);
+    $request->setIncludeAppLogs($include_app_logs);
+    $request->setCount($batch_size);
+
+    // Set version to the current version if none set explicitly.
+    if ($request->getModuleVersionSize() === 0) {
+      self::setDefaultModuleVersion($request);
+    }
+
+    return new RequestLogIterator($request);
+  }
+
+  private static function setDefaultModuleVersion($request) {
+      $mv = $request->addModuleVersion();
+      $current_module = getenv('CURRENT_MODULE_ID');
+      if ($current_module !== 'default') {
+        $mv->setModuleId($current_module);
+      }
+      $current_version = getenv('CURRENT_VERSION_ID');
+      $whole_version = explode('.', $current_version)[0];
+      $mv->setVersionId($whole_version);
+  }
+
+  /**
+   * Get request logs for the given request log ids and optionally include the
+   * application logs addded during each request. Request log ids that are not
+   * found are ignored so the returned array may have fewer items than
+   * <code>$request_ids</code>.
+   *
+   * @param mixed $request_ids A string request id or an array of string request
+   * ids obtained from <code>RequestLog::getRequestId()</code>.
+   * @param boolean $include_app_logs Should applicaiton logs be included in the
+   * fetched request logs. Defaults to true - application logs are included.
+   *
+   * @return RequestLog[] The request logs for ids that were found.
+   */
+  public static function fetchById($request_ids, $include_app_logs = true) {
+    $request = new LogReadRequest();
+    $request->setAppId(getenv('APPLICATION_ID'));
+
+    if (!is_bool($include_app_logs)) {
+      throw new \InvalidArgumentException(
+        'Parameter $include_app_logs must be boolean but was ' .
+        typeOrClass($include_app_logs));
+    }
+
+    $request->setIncludeAppLogs($include_app_logs);
+    self::setDefaultModuleVersion($request);
+
+    if (is_string($request_ids)) {
+        if (!preg_match(self::$REQUEST_ID_REGEX, $request_ids)) {
+          throw new \InvalidArgumentException(
+              "Invalid request id $request_ids");
+        }
+        $request->addRequestId($request_ids);
+    } else if (is_array($request_ids)) {
+      foreach ($request_ids as $id) {
+        if (!is_string($id)) {
+          throw new \InvalidArgumentException(
+              'Request id must be a string but was ' .
+              self::typeOrClass($id));
+        }
+        if (!preg_match(self::$REQUEST_ID_REGEX, $id)) {
+          throw new \InvalidArgumentException(
+              "Invalid request id $id");
+        }
+        $request->addRequestId($id);
+      }
+    } else {
+      throw new \InvalidArgumentException(
+          'Expected a string or an array of strings but was '.
+          self::typeOrClass($value));
+    }
+
+    $response = self::readLogs($request);
+
+    $result = [];
+    foreach ($response->getLogList() as $log) {
+      $result[] = new RequestLog($log);
+    }
+    return $result;
+  }
+
+  /**
+   * Translates a PHP <syslog>syslog<syslog> priority level into a Google App
+   * Engine severity level. Useful when filtering logs by minimum severity
+   * level given the syslog level.
+   *
+   * @param integer $syslog_level The priority level passed to
+   * <code>syslog</code>.
+   * @return integer The app engine severity level.
+   */
+  public static function getAppEngineLogLevel($syslog_level) {
+    return self::$syslog_priority_map[$syslog_level];
+  }
+
+  private static function optionTypeException($key, $value, $expected) {
+    throw new \InvalidArgumentException(
+        "Option $key must be type $expected but was " .
+        self::typeOrClass($value));
+  }
+
+  /**
+   * @return string The type or class name if type is object.
+   */
+  private static function typeOrClass($value) {
+    if (is_object($value)) {
+      return get_class($value);
+    } else {
+      return gettype($value);
+    }
+  }
+
+  private static function dateTimeToUsecs($datetime) {
+    // DateTime is accurate to seconds, StartTime to micro seconds.
+    // The time stamp may only represent a date up to 2038 due to 32 bit ints.
+    return (double) $datetime->getTimeStamp() * 1e6;
+  }
+}
+
+/**
+ * @internal
+ */
+trait ApiProxyAccess {
+  /**
+   * @param LogReadRequest $request The protocol buffer request to fetch.
+   *
+   * @return LogReadResponse The response including RequestLogs.
+   */
+  private static function readLogs(LogReadRequest $request) {
+    $response = new LogReadResponse();
+    try {
+      ApiProxy::makeSyncCall('logservice', 'Read', $request, $response);
+    } catch (ApplicationError $e) {
+      throw self::applicationErrorToException($e);
+    }
+    return $response;
+  }
+
+  private static function applicationErrorToException(ApplicationError $error) {
+    switch($error->getApplicationError()) {
+      case ErrorCode::INVALID_REQUEST:
+        return new LogException('Invalid Request');
+      case ErrorCode::STORAGE_ERROR:
+        return new LogException('Storage Error');
+      default:
+        return new LogException(
+            'Error Code: ' . $error->getApplicationError());
+    }
+  }
+}
diff --git a/php/sdk/google/appengine/api/log/LogServiceTest.php b/php/sdk/google/appengine/api/log/LogServiceTest.php
new file mode 100644
index 0000000..34df7b0
--- /dev/null
+++ b/php/sdk/google/appengine/api/log/LogServiceTest.php
@@ -0,0 +1,594 @@
+<?php
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * PHP Unit tests for the LogService.
+ *
+ */
+
+namespace google\appengine\api\log;
+
+require_once 'google/appengine/api/logservice/log_service_pb.php';
+require_once 'google/appengine/api/log/LogService.php';
+require_once 'google/appengine/api/log/RequestLog.php';
+require_once 'google/appengine/testing/ApiProxyTestBase.php';
+
+use google\appengine\LogOffset;
+use google\appengine\LogReadRequest;
+use google\appengine\LogReadResponse;
+use google\appengine\LogServiceError\ErrorCode;
+use google\appengine\runtime\ApplicationError;
+use google\appengine\testing\ApiProxyTestBase;
+
+class LogServiceTest extends ApiProxyTestBase {
+
+  const APPLICATION_ID = 'logs-test';
+  const VERSION_ID = '2.56789';
+  const MAJOR_VERSION = '2'; // derived from VERSION_ID
+
+  const RPC_PACKAGE = 'logservice';
+  const RPC_READ_METHOD = 'Read';
+  const DEFAULT_BATCH_SIZE = 20;
+
+  public function setUp() {
+    parent::setUp();
+    error_reporting(E_ALL);
+    putenv('APPLICATION_ID='. self::APPLICATION_ID);
+    putenv('CURRENT_VERSION_ID='. self::VERSION_ID);
+    putenv('CURRENT_MODULE_ID='. 'default');
+    // Default timezone must be set for DateTime
+    date_default_timezone_set('UTC');
+  }
+
+  private function createDefaultRequest($module = null) {
+    $request = new LogReadRequest();
+    $request->setAppId(self::APPLICATION_ID);
+    $mv = $request->addModuleVersion();
+    if (isset($module)) {
+      $mv->setModuleId($module);
+    }
+    $mv->setVersionId(self::MAJOR_VERSION);
+    $request->setIncludeIncomplete(false);
+    $request->setCount(self::DEFAULT_BATCH_SIZE);
+    $request->setIncludeAppLogs(false);
+
+    return $request;
+  }
+
+  private function populateLogPb($log_pb, $index) {
+    $log_pb->setCombined((string) $index);
+    $log_pb->mutableOffset()->setRequestId((string) $index);
+  }
+
+  private function checkRequestLog($log, $index) {
+    $this->assertEquals((string) $index, $log->getCombined());
+    $this->assertNotNull($log->getOffset());
+  }
+
+  public function testFetchAllLogs($module = null) {
+    $num_results = self::DEFAULT_BATCH_SIZE * 1.5;
+
+    self::expectTwoBatchFetch($num_results, $module);
+
+    $index = 0;
+    $iterator = LogService::fetch();
+    foreach ($iterator as $log) {
+      self::checkRequestLog($log, $index);
+      $index++;
+    }
+    $this->assertEquals($num_results, $index);
+    $this->apiProxyMock->verify();
+
+    // Iterate again with the same iterator and expect more API calls
+    self::expectTwoBatchFetch($num_results, $module);
+    $index = 0;
+    foreach ($iterator as $log) {
+      self::checkRequestLog($log, $index);
+      $index++;
+    }
+    $this->assertEquals($num_results, $index);
+    $this->apiProxyMock->verify();
+  }
+
+  // Create two requests and two responses
+  private function expectTwoBatchFetch($num_results, $module) {
+    $cursor = (string) self::DEFAULT_BATCH_SIZE;
+    $first_request = $this->createDefaultRequest($module);
+    $first_response = new LogReadResponse();
+    for ($i = 0; $i < self::DEFAULT_BATCH_SIZE; $i++) {
+      self::populateLogPb($first_response->addLog(), $i);
+    }
+    $first_response->mutableOffset()->setRequestId($cursor);
+
+    $second_request = $this->createDefaultRequest($module);
+    $second_request->mutableOffset()->setRequestId($cursor);
+
+    $secondResponse = new LogReadResponse();
+    for ($i = self::DEFAULT_BATCH_SIZE;
+        $i < $num_results;
+        $i++) {
+      self::populateLogPb($secondResponse->addLog(), $i);
+    }
+
+    $this->apiProxyMock->expectCall(
+        self::RPC_PACKAGE,
+        self::RPC_READ_METHOD,
+        $first_request,
+        $first_response);
+
+    $this->apiProxyMock->expectCall(
+        self::RPC_PACKAGE,
+        self::RPC_READ_METHOD,
+        $second_request,
+        $secondResponse);
+  }
+
+  public function testAllLogsNonDefaultModule() {
+    putenv('CURRENT_MODULE_ID='. 'someOtherModule');
+    self::testFetchAllLogs('someOtherModule');
+    putenv('CURRENT_MODULE_ID='. 'default');
+  }
+
+  public function testSetStartAndEndTime() {
+    $end = new \DateTime('2013-06-25');
+    $start = clone $end;
+
+    // Start time is 10 secs before end time.
+    $start->sub(\DateInterval::createFromDateString('10 seconds'));
+
+    $request = $this->createDefaultRequest();
+    $request->setStartTime($start->getTimeStamp() * 1e6);
+    $request->setEndTime($end->getTimeStamp() * 1e6);
+
+    $response = new LogReadResponse();
+    for ($i = 0; $i < 3; $i++) {
+      $log_pb = $response->addLog();
+      $log_pb->setStartTime($this->usecs($start, $i) -1e6); // sub 1 sec
+      $log_pb->setEndTime($this->usecs($start, $i));
+    }
+
+    $this->apiProxyMock->expectCall(
+        self::RPC_PACKAGE,
+        self::RPC_READ_METHOD,
+        $request,
+        $response);
+
+    // Test both DateTime and integer time types.
+    $options = [
+      'start_time' => $start,
+      'end_time' => $end->getTimestamp() * 1e6
+    ];
+
+    $count = 0;
+    foreach (LogService::fetch($options) as $log) {
+      $this->assertGreaterThanOrEqual($start, $log->getEndDateTime());
+      $this->assertLessThanOrEqual($end, $log->getEndDateTime());
+      $this->assertGreaterThanOrEqual(
+          $log->getStartDateTime()->getTimestamp() * 1e6,
+          $log->getStartTimeUsec());
+      $count++;
+    }
+
+    $this->assertEquals(3, $count);
+
+    $this->apiProxyMock->verify();
+  }
+
+  /**
+   * Create a microsecond time by adding 5 seconds per index to base DateTime.
+   */
+  private function usecs($time, $index) {
+    return ($time->getTimestamp() + $index * 5) * 1e6;
+  }
+
+  public function testAppLogs() {
+    $request = $this->createDefaultRequest();
+    $request->setIncludeAppLogs(true);
+
+    $start = new \DateTime('2013-06-25');
+
+    $response = new LogReadResponse();
+    for ($i = 0; $i < 3; $i++) {
+      $log_pb = $response->addLog();
+      for ($j = 0; $j < 5; $j++) {
+        $line_pb = $log_pb->addLine();
+        $line_pb->setLevel($j);
+        $line_pb->setLogMessage("Log $j");
+        $line_pb->setTime($this->usecs($start, $i, $j / 5));
+      }
+    }
+
+    $this->apiProxyMock->expectCall(
+        self::RPC_PACKAGE,
+        self::RPC_READ_METHOD,
+        $request,
+        $response);
+
+    $options = ['include_app_logs' => true];
+
+    $i = 0;
+    foreach (LogService::fetch($options) as $log) {
+      $lines = $log->getAppLogs();
+      $j = 0;
+      foreach ($lines as $line) {
+        $this->assertEquals($j, $line->getLevel());
+        $this->assertEquals("Log $j", $line->getMessage());
+        $this->assertEquals($this->usecs($start, $i, $j / 5),
+            $line->getTimeUsec());
+        $this->assertGreaterThanOrEqual($start, $line->getDateTime());
+        $j++;
+      }
+      $i++;
+    }
+
+    $this->apiProxyMock->verify();
+  }
+
+  public function testIncludeIncomplete() {
+    $request = $this->createDefaultRequest();
+    $request->setIncludeIncomplete(true);
+    $response = new LogReadResponse();
+    $this->apiProxyMock->expectCall(
+        self::RPC_PACKAGE,
+        self::RPC_READ_METHOD,
+        $request,
+        $response);
+
+    $options = ['include_incomplete' => true];
+
+    $result = LogService::fetch($options);
+    $this->assertInstanceOf('Iterator', $result);
+    $result->rewind();
+    $this->assertFalse($result->valid());
+
+    $this->apiProxyMock->verify();
+  }
+
+  public function testMinimumLogLevel() {
+    self::doTestMinimumLogLevel(LogService::LEVEL_CRITICAL);
+    self::doTestMinimumLogLevel(LogService::LEVEL_DEBUG);
+  }
+
+  private function doTestMinimumLogLevel($level) {
+    $request = $this->createDefaultRequest();
+    $request->setMinimumLogLevel($level);
+    $response = new LogReadResponse();
+    $this->apiProxyMock->expectCall(
+        self::RPC_PACKAGE,
+        self::RPC_READ_METHOD,
+        $request,
+        $response);
+
+    $options = ['minimum_log_level' => $level];
+
+    $result = LogService::fetch($options);
+    $this->assertInstanceOf('Iterator', $result);
+    $result->rewind();
+    $this->assertFalse($result->valid());
+
+    $this->apiProxyMock->verify();
+  }
+
+  public function testVersionIds() {
+    $request = $this->createDefaultRequest();
+    $request->clearModuleVersion();
+
+    $request->addModuleVersion()->setVersionId('v1');
+    $request->addModuleVersion()->setVersionId('v2');
+    $request->addModuleVersion()->setVersionId('v3');
+
+    $response = new LogReadResponse();
+
+    $this->apiProxyMock->expectCall(
+        self::RPC_PACKAGE,
+        self::RPC_READ_METHOD,
+        $request,
+        $response);
+
+    $options = ['versions' => ['v1', 'v2', 'v3']];
+
+    $result = LogService::fetch($options);
+
+    $result->rewind();
+    $this->assertFalse($result->valid());
+
+    $this->apiProxyMock->verify();
+  }
+
+  public function testModuleVersions() {
+    $request = $this->createDefaultRequest();
+    $request->clearModuleVersion();
+
+    $mv = $request->addModuleVersion();
+    $mv->setModuleId("m1");
+    $mv->setVersionId("v1");
+    $mv = $request->addModuleVersion();
+    $mv->setModuleId("m1");
+    $mv->setVersionId("v2");
+    $mv = $request->addModuleVersion();
+    $mv->setModuleId("m2");
+    $mv->setVersionId("v3");
+
+    $response = new LogReadResponse();
+
+    $this->apiProxyMock->expectCall(
+        self::RPC_PACKAGE,
+        self::RPC_READ_METHOD,
+        $request,
+        $response);
+
+    // Test both multiple versions and a single version.
+    $options = [
+      'module_versions' =>
+      ["m1" => ["v1", "v2"], "m2" => "v3"]
+    ];
+
+    $result = LogService::fetch($options);
+
+    $result->rewind();
+    $this->assertFalse($result->valid());
+
+    $this->apiProxyMock->verify();
+  }
+
+  public function testBatchSize() {
+    $request = $this->createDefaultRequest();
+    $request->setCount(50);
+
+    $response = new LogReadResponse();
+
+    $this->apiProxyMock->expectCall(
+        self::RPC_PACKAGE,
+        self::RPC_READ_METHOD,
+        $request,
+        $response);
+
+    $options = ['batch_size' => 50];
+    $result = LogService::fetch($options);
+    $result->rewind();
+    $this->assertFalse($result->valid());
+
+    $this->apiProxyMock->verify();
+  }
+
+  public function testOffsetEncoding() {
+    $unsafe = ' +/=';
+
+    $request = $this->createDefaultRequest();
+    $response = new LogReadResponse();
+    $response->addLog()->mutableOffset()->setRequestId($unsafe);
+
+    $this->apiProxyMock->expectCall(
+        self::RPC_PACKAGE,
+        self::RPC_READ_METHOD,
+        $request,
+        $response);
+
+    // Will only be a single result.
+    $offset;
+    foreach (LogService::fetch() as $log) {
+      $offset = $log->getOffset();
+
+      // Ensure certain non-url safe characters are not present
+      $this->assertFalse(strpbrk($offset, ' +/=:'));
+    }
+
+    $this->apiProxyMock->verify();
+
+    // Make another call using the current offset
+    $request->mutableOffset()->setRequestId($unsafe);
+    $response = new LogReadResponse();
+
+    $this->apiProxyMock->expectCall(
+        self::RPC_PACKAGE,
+        self::RPC_READ_METHOD,
+        $request,
+        $response);
+
+    $options = ['offset' => $offset];
+
+    // No results but need loop to trigger Api call
+    foreach (LogService::fetch($options) as $log) {
+    }
+
+    $this->apiProxyMock->verify();
+  }
+
+  public function testInvalidStart() {
+    $this->setExpectedException('InvalidArgumentException');
+    $options = ['start_time' => 'wrong'];
+    LogService::fetch($options);
+  }
+
+  public function testInvalidLevel() {
+    $this->setExpectedException('InvalidArgumentException');
+    $options = ['minimum_log_level' => 10];
+    LogService::fetch($options);
+  }
+
+  public function testInvalidIncomplete() {
+    $this->setExpectedException('InvalidArgumentException');
+    $options = ['include_imcomplete' => 10];
+    LogService::fetch($options);
+  }
+
+  public function testInvalidOffset() {
+    $this->setExpectedException('InvalidArgumentException');
+    $options = ['offset' => 10];
+    LogService::fetch($options);
+  }
+
+  public function testInvalidVersionIdTypes() {
+    $this->setExpectedException('InvalidArgumentException');
+    $options = ['versions' => [5, 20, 40]];
+    LogService::fetch($options);
+  }
+
+  public function testInvalidVersionIds() {
+    $this->setExpectedException('InvalidArgumentException');
+    $options = ['versions' => ['thisIsOk', 'this one is not']];
+    LogService::fetch($options);
+  }
+
+  public function testInvalidModules() {
+    $this->setExpectedException('InvalidArgumentException');
+    $options = ['module_versions' => ["foo" => false, "bar" => 9]];
+    LogService::fetch($options);
+  }
+
+  public function testOversizeBatch() {
+    $this->setExpectedException('InvalidArgumentException');
+    $options = ['batch_size' => LogService::MAX_BATCH_SIZE + 1];
+    LogService::fetch($options);
+  }
+
+  public function testSetModuleVersionsAndVersions() {
+    $this->setExpectedException('InvalidArgumentException');
+    $options = [
+      'versions' => ["foo", "bar"],
+      'module_veresions' => ["1" => "a", "2" => "b"],
+    ];
+    LogService::fetch($options);
+  }
+
+  public function testFetchByMultipleIds() {
+    $ids = [];
+    $request = new LogReadRequest();
+    $request->setAppId(self::APPLICATION_ID);
+    $request->setIncludeAppLogs(true);
+    $request->addModuleVersion()->setVersionId(self::MAJOR_VERSION);
+    $response = new LogReadResponse();
+    for ($i = 0; $i < 5; $i++) {
+      $ids[] = sprintf('%d', $i);
+      $request->addRequestId(sprintf('%d', $i));
+      self::populateLogPb($response->addLog(), $i);
+    }
+
+    $this->apiProxyMock->expectCall(
+        self::RPC_PACKAGE,
+        self::RPC_READ_METHOD,
+        $request,
+        $response);
+
+    $index = 0;
+    foreach (LogService::fetchById($ids) as $log) {
+      self::checkRequestLog($log, $index);
+      $index++;
+    }
+    $this->assertEquals(5, $index);
+    $this->apiProxyMock->verify();
+  }
+
+  public function testFetchBySingleId() {
+    $request = new LogReadRequest();
+    $request->setAppId(self::APPLICATION_ID);
+    $request->setIncludeAppLogs(true);
+    $request->addModuleVersion()->setVersionId(self::MAJOR_VERSION);
+    $response = new LogReadResponse();
+    $request->addRequestId("1A");
+    self::populateLogPb($response->addLog(), 10);
+
+    $this->apiProxyMock->expectCall(
+        self::RPC_PACKAGE,
+        self::RPC_READ_METHOD,
+        $request,
+        $response);
+
+    $index = 0;
+    foreach (LogService::fetchById("1A") as $log) {
+      self::checkRequestLog($log, 10);
+      $index++;
+    }
+    $this->assertEquals(1, $index);
+    $this->apiProxyMock->verify();
+  }
+
+  public function testFetchByIdsWithAppLogs() {
+    $ids = [];
+    $request = new LogReadRequest();
+    $request->setAppId(self::APPLICATION_ID);
+    $request->setIncludeAppLogs(true);
+    $request->addModuleVersion()->setVersionId(self::MAJOR_VERSION);
+    $response = new LogReadResponse();
+    for ($i = 0; $i < 5; $i++) {
+      $ids[] = sprintf('%d', $i);
+      $request->addRequestId(sprintf('%d', $i));
+      $this->populateLogPb($response->addLog(), $i);
+    }
+
+    $this->apiProxyMock->expectCall(
+        self::RPC_PACKAGE,
+        self::RPC_READ_METHOD,
+        $request,
+        $response);
+
+    $index = 0;
+    foreach (LogService::fetchById($ids, true) as $log) {
+      $this->checkRequestLog($log, $index);
+      $index++;
+    }
+    $this->assertEquals(5, $index);
+    $this->apiProxyMock->verify();
+  }
+
+  public function testInvalidRequestId() {
+    $this->setExpectedException('InvalidArgumentException');
+    // Request Ids must be hex values
+    LogService::fetchById("T2");
+  }
+
+  public function testApplicationError() {
+    $request = $this->createDefaultRequest();
+    $exception = new ApplicationError(ErrorCode::INVALID_REQUEST, "test");
+    $this->setExpectedException('\google\appengine\api\log\LogException',
+                                'Invalid Request');
+
+    $this->apiProxyMock->expectCall(self::RPC_PACKAGE,
+                                    self::RPC_READ_METHOD,
+                                    $request,
+                                    $exception);
+
+    foreach (LogService::fetch() as $logs) {
+      // Should never reach this due to exception.
+    }
+  }
+
+  public function testIteratorNextCalledAfter() {
+    $request = $this->createDefaultRequest();
+    $response = new LogReadResponse();
+    $response->addLog();
+    $response->addLog();
+    $response->addLog();
+
+    $this->setExpectedException('\LogicException', 'Invalid iterator state');
+
+    $this->apiProxyMock->expectCall(self::RPC_PACKAGE,
+                                    self::RPC_READ_METHOD,
+                                    $request,
+                                    $response);
+    $iterator = LogService::fetch();
+    foreach ($iterator as $log) {
+    }
+
+    $iterator->next();
+  }
+
+  public function testGetAppEngineLogLevel() {
+    $this->assertEquals(0, LogService::getAppEngineLogLevel(LOG_DEBUG));
+    $this->assertEquals(LogService::LEVEL_CRITICAL,
+                        LogService::getAppEngineLogLevel(LOG_EMERG));
+  }
+}
diff --git a/php/sdk/google/appengine/api/log/RequestLog.php b/php/sdk/google/appengine/api/log/RequestLog.php
new file mode 100644
index 0000000..9080cc8
--- /dev/null
+++ b/php/sdk/google/appengine/api/log/RequestLog.php
@@ -0,0 +1,305 @@
+<?php
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ */
+
+namespace google\appengine\api\log;
+
+require_once 'google/appengine/api/log/AppLogLine.php';
+require_once 'google/appengine/util/string_util.php';
+
+use google\appengine\util as util;
+
+/**
+ * Represents the details of a single request and may optionally contain
+ * application logs written during the request using the <code>syslog</code>
+ * function.
+ */
+final class RequestLog {
+
+  // The underlying protocol buffer.
+  private $pb;
+
+  private $app_logs;
+
+  /**
+   * @internal
+   *
+   * @param RequestLog $pb Underlying protocol buffer.
+   */
+  public function __construct(\google\appengine\RequestLog $pb) {
+    $this->pb = $pb;
+  }
+
+  /**
+   * @return string The application ID that handled this request.
+   */
+  public function getAppId() {
+    return $this->pb->getAppId();
+  }
+
+  /**
+   * @return AppLogLine[] The {@link AppLogLine}s added during this request.
+   */
+  public function getAppLogs() {
+    if (!isset($app_logs)) {
+      $app_logs = [];
+      foreach ($this->pb->getLineList() as $line) {
+        $app_logs[] = new AppLogLine($line);
+      }
+    }
+    return $app_logs;
+  }
+
+  /**
+   * @return string The Apache-format combined log entry for this request. While
+   * the information in this field can be constructed from the rest of this
+   * message, we include this method for convenience.
+   */
+  public function getCombined() {
+    return $this->pb->getCombined();
+  }
+
+  /**
+   * @return double The time at which this request began processing, in
+   * microseconds since the Unix epoch.
+   */
+  public function getStartTimeUsec() {
+    return (double) $this->pb->getStartTime();
+  }
+
+  /**
+   * @return DateTime The same value as {@link getStartTimeUsec()} as a DateTime
+   * instance accurate to the second. For greater accuracy use
+   * {@link getStartTimeUsec()}.
+   */
+  public function getStartDateTime() {
+    $result = new \DateTime();
+    $result->setTimestamp($this->getStartTimeUsec() / 1e6);
+    return $result;
+  }
+
+  /**
+   * @return double The time at which the request finished processing,
+   * in microseconds since the Unix epoch.
+   */
+  public function getEndTimeUsec() {
+    return (double) $this->pb->getEndTime();
+  }
+
+  /**
+   * @return DateTime The same value as {@link getEndTimeUsec()} as a DateTime
+   * instance accurate to the second. For greater accuracy use
+   * {@link getEndTimeUsec()}.
+   */
+  public function getEndDateTime() {
+    $result = new \DateTime();
+    $result->setTimestamp($this->getEndTimeUsec() / 1e6);
+    return $result;
+  }
+
+  /**
+   * @return string The Internet host and port number of the resource being
+   * requested.
+   */
+  public function getHost() {
+    return $this->pb->getHost();
+  }
+
+  /**
+   * @return string The HTTP version of this request.
+   */
+  public function getHttpVersion() {
+    return $this->pb->getHttpVersion();
+  }
+
+  /**
+   * @return string Mostly-unique identifier for the instance that handled the
+   * request, or the empty string.
+   */
+  public function getInstanceKey() {
+    return $this->pb->getInstanceKey();
+  }
+
+  /**
+   * @return string The origin IP address of this request.
+   * App Engine uses an origin IP address from the 0.0.0.0/8 range when the
+   * request is to a web hook.
+   * Some examples of web hooks are task queues, cron jobs and warming requests.
+   */
+  public function getIp() {
+    return $this->pb->getIp();
+  }
+
+  /**
+   * @return double The time required to process this request in microseconds.
+   */
+  public function getLatencyUsec() {
+    return (double) $this->pb->getLatency();
+  }
+
+  /**
+   * @return string The request's HTTP method (e.g., GET, PUT, POST).
+   */
+  public function getMethod() {
+    return $this->pb->getMethod();
+  }
+
+  /**
+   * @return string The nickname of the user that made the request. An empty
+   * string is returned if the user is not logged in.
+   */
+  public function getNickname() {
+    return $this->pb->getNickname();
+  }
+
+  /**
+   * @return string A url safe value that may be used as an option to
+   * <code>LogService::fetch($options)</code> to continue reading after this
+   * log.
+   */
+  public function getOffset() {
+    if ($this->pb->hasOffset()) {
+      $offset = $this->pb->getOffset()->serializeToString();
+      return util\base64UrlEncode($offset);
+    }
+    return false;
+  }
+
+  /**
+   * @return double The time, in microseconds, that this request spent in the
+   * pending request queue, if it was pending at all.
+   */
+  public function getPendingTimeUsec() {
+    return (double) $this->pb->getPendingTime();
+  }
+
+  /**
+   * @return string The referrer URL of this request.
+   */
+  public function getReferrer() {
+    return $this->pb->getReferrer();
+  }
+
+  /**
+   * @return integer The module instance that handled the request if
+   * manual_scaling or basic_scaling is configured or -1 for automatic_scaling.
+   */
+  public function getInstanceIndex() {
+    return $this->pb->getReplicaIndex();
+  }
+
+  /**
+   * @return string A globally unique identifier for a request, based on the
+   * request's starting time.
+   */
+  public function getRequestId() {
+    return $this->pb->getRequestId();
+  }
+
+  /**
+   * @return string The resource path on the server requested by the client.
+   * Contains only the path component of the request URL.
+   */
+  public function getResource() {
+    return $this->pb->getResource();
+  }
+
+  /**
+   * @return integer The size (in bytes) of the response sent back to the
+   * client.
+   */
+  public function getResponseSize() {
+    return $this->pb->getResponseSize();
+  }
+
+  /**
+   * @return integer The HTTP response status of this request.
+   */
+  public function getStatus() {
+    return $this->pb->getStatus();
+  }
+
+  /**
+   * @return string The request's task name, if this request was generated via
+   * the Task Queue API.
+   */
+  public function getTaskName() {
+    return $this->pb->getTaskName();
+  }
+
+  /**
+   * @return string The request's queue name, if this request was generated via
+   * the Task Queue API.
+   */
+  public function getTaskQueueName() {
+    return $this->pb->getTaskQueueName();
+  }
+
+  /**
+   * @return string The file or class within the URL mapping used for this
+   * request.
+   * Useful for tracking down the source code which was responsible for
+   * managing the request, especially for multiply mapped handlers.
+   */
+  public function getUrlMapEntry() {
+    return $this->pb->getUrlMapEntry();
+  }
+
+  /**
+   * @return string The user agent used to make this request.
+   */
+  public function getUserAgent() {
+    return $this->pb->getUserAgent();
+  }
+
+  /**
+   * @return string The version of the application that handled this request.
+   */
+  public function getVersionId() {
+    return $this->pb->getVersionId();
+  }
+
+  /**
+   * @return string The version of the application that handled this request.
+   */
+  public function getModuleId() {
+    return $this->pb->getModuleId();
+  }
+
+  /**
+   * @return boolean Whether or not this request has finished processing. If
+   * not, this request is still active.
+   */
+  public function isFinished() {
+    return $this->pb->getFinished();
+  }
+
+  /**
+   * @return boolean Whether or not this request was a loading request.
+   */
+  public function isLoadingRequest() {
+    return $this->pb->getWasLoadingRequest();
+  }
+
+  /**
+   * @return string App Engine Release, e.g. "1.8.4"
+   */
+  public function getAppEngineRelease() {
+    return $this->pb->getAppEngineRelease();
+  }
+}
diff --git a/php/sdk/google/appengine/api/log/RequestLogIterator.php b/php/sdk/google/appengine/api/log/RequestLogIterator.php
new file mode 100644
index 0000000..9365895
--- /dev/null
+++ b/php/sdk/google/appengine/api/log/RequestLogIterator.php
@@ -0,0 +1,137 @@
+<?php
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ */
+
+namespace google\appengine\api\log;
+
+require_once 'google/appengine/api/log/LogService.php';
+require_once 'google/appengine/api/log/RequestLog.php';
+
+use google\appengine\LogReadRequest;
+use google\appengine\LogReadResponse;
+use google\appengine\runtime\ApiProxy;
+
+/**
+ * Allows request logs to be iterated using a standard foreach statement but
+ * fetches the results in batches as needed.
+ */
+final class RequestLogIterator implements \Iterator {
+  use ApiProxyAccess;
+
+  // Protocol buffer request containing the parsed fetch options.
+  private $request;
+
+  // Original offset value of the request to allow rewind() to return to.
+  private $origin;
+
+  // The current response including request logs.
+  private $response = null;
+
+  // Index into the request logs included in the $response.
+  private $position = 0;
+
+  // Cached current request log in case current() is called multiple times.
+  private $current = null;
+
+  /**
+   * @internal
+   *
+   * @param LogReadRequest $request The underlying protocol buffer.
+   */
+  public function __construct(\google\appengine\LogReadRequest $request) {
+    $this->request = $request;
+
+    // Remember start position to allow rewind() to return.
+    if ($request->hasOffset()) {
+      $this->origin = $request->getOffset();
+    }
+  }
+
+  public function current() {
+    if (!$this->valid()) {
+      throw new \LogicException('Invalid iterator state');
+    }
+
+    // Maintain current item in case current called multiple times.
+    if ($this->current === null) {
+      $this->current = new RequestLog($this->response->getLog($this->position));
+    }
+    return $this->current;
+  }
+
+  public function next() {
+    if (!$this->valid()) {
+      throw new \LogicException('Invalid iterator state');
+    }
+
+    // Free up memory.
+    $this->response->getLogList()[$this->position] = null;
+    $this->current = null;
+    $this->position++;
+
+    // Fetch more logs if needed and we have not read all (has offset).
+    if ($this->position >= $this->response->getLogSize() &&
+        $this->response->hasOffset()) {
+      $this->fetch();
+    }
+  }
+
+  private function fetch() {
+    $this->response = self::readLogs($this->request);
+
+    // Update the request with the new offset for next fetch.
+    if ($this->response->hasOffset()) {
+      $new_offset = $this->response->getOffset();
+      $this->request->mutableOffset()->copyFrom($new_offset);
+    }
+    $this->position = 0;
+  }
+
+  public function valid() {
+    // Response can only be null after rewind.
+    if ($this->response === null) {
+      $this->fetch();
+    }
+    return $this->position < $this->response->getLogSize();
+  }
+
+  /**
+   * Reset to initial state. Called at start of foreach statement.
+   */
+  public function rewind() {
+    // Reset request to start from original offset.
+    if ($this->response !== null) {
+      if (isset($this->origin)) {
+        $this->request->getOffset()->copyFrom($this->origin);
+      } else {
+        $this->request->clearOffset();
+      }
+    }
+
+    $this->current = null;
+    $this->response = null;
+    $this->position = 0;
+  }
+
+  /**
+   * @return null Non-associative iterator.
+   */
+  public function key() {
+    return null;
+  }
+}
diff --git a/php/sdk/google/appengine/api/logservice/log_service_pb.php b/php/sdk/google/appengine/api/logservice/log_service_pb.php
new file mode 100644
index 0000000..96f119c
--- /dev/null
+++ b/php/sdk/google/appengine/api/logservice/log_service_pb.php
@@ -0,0 +1,4159 @@
+<?php
+/**
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: google/appengine/api/logservice/log_service.proto
+
+namespace dummy {
+  require_once 'google/appengine/runtime/proto/ProtocolMessage.php';
+  require_once 'google/appengine/api/api_base_pb.php';
+}
+namespace google\appengine\LogServiceError {
+  class ErrorCode {
+    const OK = 0;
+    const INVALID_REQUEST = 1;
+    const STORAGE_ERROR = 2;
+  }
+}
+namespace google\appengine {
+  class LogServiceError extends \google\net\ProtocolMessage {
+    public function clear() {
+    }
+    public function byteSizePartial() {
+      $res = 0;
+      return $res;
+    }
+    public function outputPartial($out) {
+    }
+    public function tryMerge($d) {
+      while($d->avail() > 0) {
+        $tt = $d->getVarInt32();
+        switch ($tt) {
+          case 0:
+            throw new \google\net\ProtocolBufferDecodeError();
+            break;
+          default:
+            $d->skipData($tt);
+        }
+      };
+    }
+    public function checkInitialized() {
+      return null;
+    }
+    public function mergeFrom($x) {
+      if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); }
+    }
+    public function equals($x) {
+      if ($x === $this) { return true; }
+      return true;
+    }
+    public function shortDebugString($prefix = "") {
+      $res = '';
+      return $res;
+    }
+  }
+}
+namespace google\appengine {
+  class UserAppLogLine extends \google\net\ProtocolMessage {
+    public function getTimestampUsec() {
+      if (!isset($this->timestamp_usec)) {
+        return "0";
+      }
+      return $this->timestamp_usec;
+    }
+    public function setTimestampUsec($val) {
+      if (is_double($val)) {
+        $this->timestamp_usec = sprintf('%0.0F', $val);
+      } else {
+        $this->timestamp_usec = $val;
+      }
+      return $this;
+    }
+    public function clearTimestampUsec() {
+      unset($this->timestamp_usec);
+      return $this;
+    }
+    public function hasTimestampUsec() {
+      return isset($this->timestamp_usec);
+    }
+    public function getLevel() {
+      if (!isset($this->level)) {
+        return "0";
+      }
+      return $this->level;
+    }
+    public function setLevel($val) {
+      if (is_double($val)) {
+        $this->level = sprintf('%0.0F', $val);
+      } else {
+        $this->level = $val;
+      }
+      return $this;
+    }
+    public function clearLevel() {
+      unset($this->level);
+      return $this;
+    }
+    public function hasLevel() {
+      return isset($this->level);
+    }
+    public function getMessage() {
+      if (!isset($this->message)) {
+        return '';
+      }
+      return $this->message;
+    }
+    public function setMessage($val) {
+      $this->message = $val;
+      return $this;
+    }
+    public function clearMessage() {
+      unset($this->message);
+      return $this;
+    }
+    public function hasMessage() {
+      return isset($this->message);
+    }
+    public function clear() {
+      $this->clearTimestampUsec();
+      $this->clearLevel();
+      $this->clearMessage();
+    }
+    public function byteSizePartial() {
+      $res = 0;
+      if (isset($this->timestamp_usec)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->timestamp_usec);
+      }
+      if (isset($this->level)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->level);
+      }
+      if (isset($this->message)) {
+        $res += 1;
+        $res += $this->lengthString(strlen($this->message));
+      }
+      return $res;
+    }
+    public function outputPartial($out) {
+      if (isset($this->timestamp_usec)) {
+        $out->putVarInt32(8);
+        $out->putVarInt64($this->timestamp_usec);
+      }
+      if (isset($this->level)) {
+        $out->putVarInt32(16);
+        $out->putVarInt64($this->level);
+      }
+      if (isset($this->message)) {
+        $out->putVarInt32(26);
+        $out->putPrefixedString($this->message);
+      }
+    }
+    public function tryMerge($d) {
+      while($d->avail() > 0) {
+        $tt = $d->getVarInt32();
+        switch ($tt) {
+          case 8:
+            $this->setTimestampUsec($d->getVarInt64());
+            break;
+          case 16:
+            $this->setLevel($d->getVarInt64());
+            break;
+          case 26:
+            $length = $d->getVarInt32();
+            $this->setMessage(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 0:
+            throw new \google\net\ProtocolBufferDecodeError();
+            break;
+          default:
+            $d->skipData($tt);
+        }
+      };
+    }
+    public function checkInitialized() {
+      if (!isset($this->timestamp_usec)) return 'timestamp_usec';
+      if (!isset($this->level)) return 'level';
+      if (!isset($this->message)) return 'message';
+      return null;
+    }
+    public function mergeFrom($x) {
+      if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); }
+      if ($x->hasTimestampUsec()) {
+        $this->setTimestampUsec($x->getTimestampUsec());
+      }
+      if ($x->hasLevel()) {
+        $this->setLevel($x->getLevel());
+      }
+      if ($x->hasMessage()) {
+        $this->setMessage($x->getMessage());
+      }
+    }
+    public function equals($x) {
+      if ($x === $this) { return true; }
+      if (isset($this->timestamp_usec) !== isset($x->timestamp_usec)) return false;
+      if (isset($this->timestamp_usec) && !$this->integerEquals($this->timestamp_usec, $x->timestamp_usec)) return false;
+      if (isset($this->level) !== isset($x->level)) return false;
+      if (isset($this->level) && !$this->integerEquals($this->level, $x->level)) return false;
+      if (isset($this->message) !== isset($x->message)) return false;
+      if (isset($this->message) && $this->message !== $x->message) return false;
+      return true;
+    }
+    public function shortDebugString($prefix = "") {
+      $res = '';
+      if (isset($this->timestamp_usec)) {
+        $res .= $prefix . "timestamp_usec: " . $this->debugFormatInt64($this->timestamp_usec) . "\n";
+      }
+      if (isset($this->level)) {
+        $res .= $prefix . "level: " . $this->debugFormatInt64($this->level) . "\n";
+      }
+      if (isset($this->message)) {
+        $res .= $prefix . "message: " . $this->debugFormatString($this->message) . "\n";
+      }
+      return $res;
+    }
+  }
+}
+namespace google\appengine {
+  class UserAppLogGroup extends \google\net\ProtocolMessage {
+    private $log_line = array();
+    public function getLogLineSize() {
+      return sizeof($this->log_line);
+    }
+    public function getLogLineList() {
+      return $this->log_line;
+    }
+    public function mutableLogLine($idx) {
+      if (!isset($this->log_line[$idx])) {
+        $val = new \google\appengine\UserAppLogLine();
+        $this->log_line[$idx] = $val;
+        return $val;
+      }
+      return $this->log_line[$idx];
+    }
+    public function getLogLine($idx) {
+      if (isset($this->log_line[$idx])) {
+        return $this->log_line[$idx];
+      }
+      if ($idx >= end(array_keys($this->log_line))) {
+        throw new \OutOfRangeException('index out of range: ' + $idx);
+      }
+      return new \google\appengine\UserAppLogLine();
+    }
+    public function addLogLine() {
+      $val = new \google\appengine\UserAppLogLine();
+      $this->log_line[] = $val;
+      return $val;
+    }
+    public function clearLogLine() {
+      $this->log_line = array();
+    }
+    public function clear() {
+      $this->clearLogLine();
+    }
+    public function byteSizePartial() {
+      $res = 0;
+      $this->checkProtoArray($this->log_line);
+      $res += 1 * sizeof($this->log_line);
+      foreach ($this->log_line as $value) {
+        $res += $this->lengthString($value->byteSizePartial());
+      }
+      return $res;
+    }
+    public function outputPartial($out) {
+      $this->checkProtoArray($this->log_line);
+      foreach ($this->log_line as $value) {
+        $out->putVarInt32(18);
+        $out->putVarInt32($value->byteSizePartial());
+        $value->outputPartial($out);
+      }
+    }
+    public function tryMerge($d) {
+      while($d->avail() > 0) {
+        $tt = $d->getVarInt32();
+        switch ($tt) {
+          case 18:
+            $length = $d->getVarInt32();
+            $tmp = new \google\net\Decoder($d->buffer(), $d->pos(), $d->pos() + $length);
+            $d->skip($length);
+            $this->addLogLine()->tryMerge($tmp);
+            break;
+          case 0:
+            throw new \google\net\ProtocolBufferDecodeError();
+            break;
+          default:
+            $d->skipData($tt);
+        }
+      };
+    }
+    public function checkInitialized() {
+      foreach ($this->log_line as $value) {
+        if (!$value->isInitialized()) return 'log_line';
+      }
+      return null;
+    }
+    public function mergeFrom($x) {
+      if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); }
+      foreach ($x->getLogLineList() as $v) {
+        $this->addLogLine()->copyFrom($v);
+      }
+    }
+    public function equals($x) {
+      if ($x === $this) { return true; }
+      if (sizeof($this->log_line) !== sizeof($x->log_line)) return false;
+      foreach (array_map(null, $this->log_line, $x->log_line) as $v) {
+        if (!$v[0]->equals($v[1])) return false;
+      }
+      return true;
+    }
+    public function shortDebugString($prefix = "") {
+      $res = '';
+      foreach ($this->log_line as $value) {
+        $res .= $prefix . "log_line <\n" . $value->shortDebugString($prefix . "  ") . $prefix . ">\n";
+      }
+      return $res;
+    }
+  }
+}
+namespace google\appengine {
+  class FlushRequest extends \google\net\ProtocolMessage {
+    public function getLogs() {
+      if (!isset($this->logs)) {
+        return '';
+      }
+      return $this->logs;
+    }
+    public function setLogs($val) {
+      $this->logs = $val;
+      return $this;
+    }
+    public function clearLogs() {
+      unset($this->logs);
+      return $this;
+    }
+    public function hasLogs() {
+      return isset($this->logs);
+    }
+    public function clear() {
+      $this->clearLogs();
+    }
+    public function byteSizePartial() {
+      $res = 0;
+      if (isset($this->logs)) {
+        $res += 1;
+        $res += $this->lengthString(strlen($this->logs));
+      }
+      return $res;
+    }
+    public function outputPartial($out) {
+      if (isset($this->logs)) {
+        $out->putVarInt32(10);
+        $out->putPrefixedString($this->logs);
+      }
+    }
+    public function tryMerge($d) {
+      while($d->avail() > 0) {
+        $tt = $d->getVarInt32();
+        switch ($tt) {
+          case 10:
+            $length = $d->getVarInt32();
+            $this->setLogs(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 0:
+            throw new \google\net\ProtocolBufferDecodeError();
+            break;
+          default:
+            $d->skipData($tt);
+        }
+      };
+    }
+    public function checkInitialized() {
+      return null;
+    }
+    public function mergeFrom($x) {
+      if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); }
+      if ($x->hasLogs()) {
+        $this->setLogs($x->getLogs());
+      }
+    }
+    public function equals($x) {
+      if ($x === $this) { return true; }
+      if (isset($this->logs) !== isset($x->logs)) return false;
+      if (isset($this->logs) && $this->logs !== $x->logs) return false;
+      return true;
+    }
+    public function shortDebugString($prefix = "") {
+      $res = '';
+      if (isset($this->logs)) {
+        $res .= $prefix . "logs: " . $this->debugFormatString($this->logs) . "\n";
+      }
+      return $res;
+    }
+  }
+}
+namespace google\appengine {
+  class SetStatusRequest extends \google\net\ProtocolMessage {
+    public function getStatus() {
+      if (!isset($this->status)) {
+        return '';
+      }
+      return $this->status;
+    }
+    public function setStatus($val) {
+      $this->status = $val;
+      return $this;
+    }
+    public function clearStatus() {
+      unset($this->status);
+      return $this;
+    }
+    public function hasStatus() {
+      return isset($this->status);
+    }
+    public function clear() {
+      $this->clearStatus();
+    }
+    public function byteSizePartial() {
+      $res = 0;
+      if (isset($this->status)) {
+        $res += 1;
+        $res += $this->lengthString(strlen($this->status));
+      }
+      return $res;
+    }
+    public function outputPartial($out) {
+      if (isset($this->status)) {
+        $out->putVarInt32(10);
+        $out->putPrefixedString($this->status);
+      }
+    }
+    public function tryMerge($d) {
+      while($d->avail() > 0) {
+        $tt = $d->getVarInt32();
+        switch ($tt) {
+          case 10:
+            $length = $d->getVarInt32();
+            $this->setStatus(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 0:
+            throw new \google\net\ProtocolBufferDecodeError();
+            break;
+          default:
+            $d->skipData($tt);
+        }
+      };
+    }
+    public function checkInitialized() {
+      if (!isset($this->status)) return 'status';
+      return null;
+    }
+    public function mergeFrom($x) {
+      if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); }
+      if ($x->hasStatus()) {
+        $this->setStatus($x->getStatus());
+      }
+    }
+    public function equals($x) {
+      if ($x === $this) { return true; }
+      if (isset($this->status) !== isset($x->status)) return false;
+      if (isset($this->status) && $this->status !== $x->status) return false;
+      return true;
+    }
+    public function shortDebugString($prefix = "") {
+      $res = '';
+      if (isset($this->status)) {
+        $res .= $prefix . "status: " . $this->debugFormatString($this->status) . "\n";
+      }
+      return $res;
+    }
+  }
+}
+namespace google\appengine {
+  class LogOffset extends \google\net\ProtocolMessage {
+    public function getRequestId() {
+      if (!isset($this->request_id)) {
+        return '';
+      }
+      return $this->request_id;
+    }
+    public function setRequestId($val) {
+      $this->request_id = $val;
+      return $this;
+    }
+    public function clearRequestId() {
+      unset($this->request_id);
+      return $this;
+    }
+    public function hasRequestId() {
+      return isset($this->request_id);
+    }
+    public function clear() {
+      $this->clearRequestId();
+    }
+    public function byteSizePartial() {
+      $res = 0;
+      if (isset($this->request_id)) {
+        $res += 1;
+        $res += $this->lengthString(strlen($this->request_id));
+      }
+      return $res;
+    }
+    public function outputPartial($out) {
+      if (isset($this->request_id)) {
+        $out->putVarInt32(10);
+        $out->putPrefixedString($this->request_id);
+      }
+    }
+    public function tryMerge($d) {
+      while($d->avail() > 0) {
+        $tt = $d->getVarInt32();
+        switch ($tt) {
+          case 10:
+            $length = $d->getVarInt32();
+            $this->setRequestId(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 0:
+            throw new \google\net\ProtocolBufferDecodeError();
+            break;
+          default:
+            $d->skipData($tt);
+        }
+      };
+    }
+    public function checkInitialized() {
+      return null;
+    }
+    public function mergeFrom($x) {
+      if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); }
+      if ($x->hasRequestId()) {
+        $this->setRequestId($x->getRequestId());
+      }
+    }
+    public function equals($x) {
+      if ($x === $this) { return true; }
+      if (isset($this->request_id) !== isset($x->request_id)) return false;
+      if (isset($this->request_id) && $this->request_id !== $x->request_id) return false;
+      return true;
+    }
+    public function shortDebugString($prefix = "") {
+      $res = '';
+      if (isset($this->request_id)) {
+        $res .= $prefix . "request_id: " . $this->debugFormatString($this->request_id) . "\n";
+      }
+      return $res;
+    }
+  }
+}
+namespace google\appengine {
+  class LogLine extends \google\net\ProtocolMessage {
+    public function getTime() {
+      if (!isset($this->time)) {
+        return "0";
+      }
+      return $this->time;
+    }
+    public function setTime($val) {
+      if (is_double($val)) {
+        $this->time = sprintf('%0.0F', $val);
+      } else {
+        $this->time = $val;
+      }
+      return $this;
+    }
+    public function clearTime() {
+      unset($this->time);
+      return $this;
+    }
+    public function hasTime() {
+      return isset($this->time);
+    }
+    public function getLevel() {
+      if (!isset($this->level)) {
+        return 0;
+      }
+      return $this->level;
+    }
+    public function setLevel($val) {
+      $this->level = $val;
+      return $this;
+    }
+    public function clearLevel() {
+      unset($this->level);
+      return $this;
+    }
+    public function hasLevel() {
+      return isset($this->level);
+    }
+    public function getLogMessage() {
+      if (!isset($this->log_message)) {
+        return '';
+      }
+      return $this->log_message;
+    }
+    public function setLogMessage($val) {
+      $this->log_message = $val;
+      return $this;
+    }
+    public function clearLogMessage() {
+      unset($this->log_message);
+      return $this;
+    }
+    public function hasLogMessage() {
+      return isset($this->log_message);
+    }
+    public function clear() {
+      $this->clearTime();
+      $this->clearLevel();
+      $this->clearLogMessage();
+    }
+    public function byteSizePartial() {
+      $res = 0;
+      if (isset($this->time)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->time);
+      }
+      if (isset($this->level)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->level);
+      }
+      if (isset($this->log_message)) {
+        $res += 1;
+        $res += $this->lengthString(strlen($this->log_message));
+      }
+      return $res;
+    }
+    public function outputPartial($out) {
+      if (isset($this->time)) {
+        $out->putVarInt32(8);
+        $out->putVarInt64($this->time);
+      }
+      if (isset($this->level)) {
+        $out->putVarInt32(16);
+        $out->putVarInt32($this->level);
+      }
+      if (isset($this->log_message)) {
+        $out->putVarInt32(26);
+        $out->putPrefixedString($this->log_message);
+      }
+    }
+    public function tryMerge($d) {
+      while($d->avail() > 0) {
+        $tt = $d->getVarInt32();
+        switch ($tt) {
+          case 8:
+            $this->setTime($d->getVarInt64());
+            break;
+          case 16:
+            $this->setLevel($d->getVarInt32());
+            break;
+          case 26:
+            $length = $d->getVarInt32();
+            $this->setLogMessage(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 0:
+            throw new \google\net\ProtocolBufferDecodeError();
+            break;
+          default:
+            $d->skipData($tt);
+        }
+      };
+    }
+    public function checkInitialized() {
+      if (!isset($this->time)) return 'time';
+      if (!isset($this->level)) return 'level';
+      if (!isset($this->log_message)) return 'log_message';
+      return null;
+    }
+    public function mergeFrom($x) {
+      if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); }
+      if ($x->hasTime()) {
+        $this->setTime($x->getTime());
+      }
+      if ($x->hasLevel()) {
+        $this->setLevel($x->getLevel());
+      }
+      if ($x->hasLogMessage()) {
+        $this->setLogMessage($x->getLogMessage());
+      }
+    }
+    public function equals($x) {
+      if ($x === $this) { return true; }
+      if (isset($this->time) !== isset($x->time)) return false;
+      if (isset($this->time) && !$this->integerEquals($this->time, $x->time)) return false;
+      if (isset($this->level) !== isset($x->level)) return false;
+      if (isset($this->level) && !$this->integerEquals($this->level, $x->level)) return false;
+      if (isset($this->log_message) !== isset($x->log_message)) return false;
+      if (isset($this->log_message) && $this->log_message !== $x->log_message) return false;
+      return true;
+    }
+    public function shortDebugString($prefix = "") {
+      $res = '';
+      if (isset($this->time)) {
+        $res .= $prefix . "time: " . $this->debugFormatInt64($this->time) . "\n";
+      }
+      if (isset($this->level)) {
+        $res .= $prefix . "level: " . $this->debugFormatInt32($this->level) . "\n";
+      }
+      if (isset($this->log_message)) {
+        $res .= $prefix . "log_message: " . $this->debugFormatString($this->log_message) . "\n";
+      }
+      return $res;
+    }
+  }
+}
+namespace google\appengine {
+  class RequestLog extends \google\net\ProtocolMessage {
+    private $line = array();
+    public function getAppId() {
+      if (!isset($this->app_id)) {
+        return '';
+      }
+      return $this->app_id;
+    }
+    public function setAppId($val) {
+      $this->app_id = $val;
+      return $this;
+    }
+    public function clearAppId() {
+      unset($this->app_id);
+      return $this;
+    }
+    public function hasAppId() {
+      return isset($this->app_id);
+    }
+    public function getVersionId() {
+      if (!isset($this->version_id)) {
+        return '';
+      }
+      return $this->version_id;
+    }
+    public function setVersionId($val) {
+      $this->version_id = $val;
+      return $this;
+    }
+    public function clearVersionId() {
+      unset($this->version_id);
+      return $this;
+    }
+    public function hasVersionId() {
+      return isset($this->version_id);
+    }
+    public function getRequestId() {
+      if (!isset($this->request_id)) {
+        return '';
+      }
+      return $this->request_id;
+    }
+    public function setRequestId($val) {
+      $this->request_id = $val;
+      return $this;
+    }
+    public function clearRequestId() {
+      unset($this->request_id);
+      return $this;
+    }
+    public function hasRequestId() {
+      return isset($this->request_id);
+    }
+    public function getIp() {
+      if (!isset($this->ip)) {
+        return '';
+      }
+      return $this->ip;
+    }
+    public function setIp($val) {
+      $this->ip = $val;
+      return $this;
+    }
+    public function clearIp() {
+      unset($this->ip);
+      return $this;
+    }
+    public function hasIp() {
+      return isset($this->ip);
+    }
+    public function getNickname() {
+      if (!isset($this->nickname)) {
+        return '';
+      }
+      return $this->nickname;
+    }
+    public function setNickname($val) {
+      $this->nickname = $val;
+      return $this;
+    }
+    public function clearNickname() {
+      unset($this->nickname);
+      return $this;
+    }
+    public function hasNickname() {
+      return isset($this->nickname);
+    }
+    public function getStartTime() {
+      if (!isset($this->start_time)) {
+        return "0";
+      }
+      return $this->start_time;
+    }
+    public function setStartTime($val) {
+      if (is_double($val)) {
+        $this->start_time = sprintf('%0.0F', $val);
+      } else {
+        $this->start_time = $val;
+      }
+      return $this;
+    }
+    public function clearStartTime() {
+      unset($this->start_time);
+      return $this;
+    }
+    public function hasStartTime() {
+      return isset($this->start_time);
+    }
+    public function getEndTime() {
+      if (!isset($this->end_time)) {
+        return "0";
+      }
+      return $this->end_time;
+    }
+    public function setEndTime($val) {
+      if (is_double($val)) {
+        $this->end_time = sprintf('%0.0F', $val);
+      } else {
+        $this->end_time = $val;
+      }
+      return $this;
+    }
+    public function clearEndTime() {
+      unset($this->end_time);
+      return $this;
+    }
+    public function hasEndTime() {
+      return isset($this->end_time);
+    }
+    public function getLatency() {
+      if (!isset($this->latency)) {
+        return "0";
+      }
+      return $this->latency;
+    }
+    public function setLatency($val) {
+      if (is_double($val)) {
+        $this->latency = sprintf('%0.0F', $val);
+      } else {
+        $this->latency = $val;
+      }
+      return $this;
+    }
+    public function clearLatency() {
+      unset($this->latency);
+      return $this;
+    }
+    public function hasLatency() {
+      return isset($this->latency);
+    }
+    public function getMcycles() {
+      if (!isset($this->mcycles)) {
+        return "0";
+      }
+      return $this->mcycles;
+    }
+    public function setMcycles($val) {
+      if (is_double($val)) {
+        $this->mcycles = sprintf('%0.0F', $val);
+      } else {
+        $this->mcycles = $val;
+      }
+      return $this;
+    }
+    public function clearMcycles() {
+      unset($this->mcycles);
+      return $this;
+    }
+    public function hasMcycles() {
+      return isset($this->mcycles);
+    }
+    public function getMethod() {
+      if (!isset($this->method)) {
+        return '';
+      }
+      return $this->method;
+    }
+    public function setMethod($val) {
+      $this->method = $val;
+      return $this;
+    }
+    public function clearMethod() {
+      unset($this->method);
+      return $this;
+    }
+    public function hasMethod() {
+      return isset($this->method);
+    }
+    public function getResource() {
+      if (!isset($this->resource)) {
+        return '';
+      }
+      return $this->resource;
+    }
+    public function setResource($val) {
+      $this->resource = $val;
+      return $this;
+    }
+    public function clearResource() {
+      unset($this->resource);
+      return $this;
+    }
+    public function hasResource() {
+      return isset($this->resource);
+    }
+    public function getHttpVersion() {
+      if (!isset($this->http_version)) {
+        return '';
+      }
+      return $this->http_version;
+    }
+    public function setHttpVersion($val) {
+      $this->http_version = $val;
+      return $this;
+    }
+    public function clearHttpVersion() {
+      unset($this->http_version);
+      return $this;
+    }
+    public function hasHttpVersion() {
+      return isset($this->http_version);
+    }
+    public function getStatus() {
+      if (!isset($this->status)) {
+        return 0;
+      }
+      return $this->status;
+    }
+    public function setStatus($val) {
+      $this->status = $val;
+      return $this;
+    }
+    public function clearStatus() {
+      unset($this->status);
+      return $this;
+    }
+    public function hasStatus() {
+      return isset($this->status);
+    }
+    public function getResponseSize() {
+      if (!isset($this->response_size)) {
+        return "0";
+      }
+      return $this->response_size;
+    }
+    public function setResponseSize($val) {
+      if (is_double($val)) {
+        $this->response_size = sprintf('%0.0F', $val);
+      } else {
+        $this->response_size = $val;
+      }
+      return $this;
+    }
+    public function clearResponseSize() {
+      unset($this->response_size);
+      return $this;
+    }
+    public function hasResponseSize() {
+      return isset($this->response_size);
+    }
+    public function getReferrer() {
+      if (!isset($this->referrer)) {
+        return '';
+      }
+      return $this->referrer;
+    }
+    public function setReferrer($val) {
+      $this->referrer = $val;
+      return $this;
+    }
+    public function clearReferrer() {
+      unset($this->referrer);
+      return $this;
+    }
+    public function hasReferrer() {
+      return isset($this->referrer);
+    }
+    public function getUserAgent() {
+      if (!isset($this->user_agent)) {
+        return '';
+      }
+      return $this->user_agent;
+    }
+    public function setUserAgent($val) {
+      $this->user_agent = $val;
+      return $this;
+    }
+    public function clearUserAgent() {
+      unset($this->user_agent);
+      return $this;
+    }
+    public function hasUserAgent() {
+      return isset($this->user_agent);
+    }
+    public function getUrlMapEntry() {
+      if (!isset($this->url_map_entry)) {
+        return '';
+      }
+      return $this->url_map_entry;
+    }
+    public function setUrlMapEntry($val) {
+      $this->url_map_entry = $val;
+      return $this;
+    }
+    public function clearUrlMapEntry() {
+      unset($this->url_map_entry);
+      return $this;
+    }
+    public function hasUrlMapEntry() {
+      return isset($this->url_map_entry);
+    }
+    public function getCombined() {
+      if (!isset($this->combined)) {
+        return '';
+      }
+      return $this->combined;
+    }
+    public function setCombined($val) {
+      $this->combined = $val;
+      return $this;
+    }
+    public function clearCombined() {
+      unset($this->combined);
+      return $this;
+    }
+    public function hasCombined() {
+      return isset($this->combined);
+    }
+    public function getApiMcycles() {
+      if (!isset($this->api_mcycles)) {
+        return "0";
+      }
+      return $this->api_mcycles;
+    }
+    public function setApiMcycles($val) {
+      if (is_double($val)) {
+        $this->api_mcycles = sprintf('%0.0F', $val);
+      } else {
+        $this->api_mcycles = $val;
+      }
+      return $this;
+    }
+    public function clearApiMcycles() {
+      unset($this->api_mcycles);
+      return $this;
+    }
+    public function hasApiMcycles() {
+      return isset($this->api_mcycles);
+    }
+    public function getHost() {
+      if (!isset($this->host)) {
+        return '';
+      }
+      return $this->host;
+    }
+    public function setHost($val) {
+      $this->host = $val;
+      return $this;
+    }
+    public function clearHost() {
+      unset($this->host);
+      return $this;
+    }
+    public function hasHost() {
+      return isset($this->host);
+    }
+    public function getCost() {
+      if (!isset($this->cost)) {
+        return 0.0;
+      }
+      return $this->cost;
+    }
+    public function setCost($val) {
+      $this->cost = $val;
+      return $this;
+    }
+    public function clearCost() {
+      unset($this->cost);
+      return $this;
+    }
+    public function hasCost() {
+      return isset($this->cost);
+    }
+    public function getTaskQueueName() {
+      if (!isset($this->task_queue_name)) {
+        return '';
+      }
+      return $this->task_queue_name;
+    }
+    public function setTaskQueueName($val) {
+      $this->task_queue_name = $val;
+      return $this;
+    }
+    public function clearTaskQueueName() {
+      unset($this->task_queue_name);
+      return $this;
+    }
+    public function hasTaskQueueName() {
+      return isset($this->task_queue_name);
+    }
+    public function getTaskName() {
+      if (!isset($this->task_name)) {
+        return '';
+      }
+      return $this->task_name;
+    }
+    public function setTaskName($val) {
+      $this->task_name = $val;
+      return $this;
+    }
+    public function clearTaskName() {
+      unset($this->task_name);
+      return $this;
+    }
+    public function hasTaskName() {
+      return isset($this->task_name);
+    }
+    public function getWasLoadingRequest() {
+      if (!isset($this->was_loading_request)) {
+        return false;
+      }
+      return $this->was_loading_request;
+    }
+    public function setWasLoadingRequest($val) {
+      $this->was_loading_request = $val;
+      return $this;
+    }
+    public function clearWasLoadingRequest() {
+      unset($this->was_loading_request);
+      return $this;
+    }
+    public function hasWasLoadingRequest() {
+      return isset($this->was_loading_request);
+    }
+    public function getPendingTime() {
+      if (!isset($this->pending_time)) {
+        return "0";
+      }
+      return $this->pending_time;
+    }
+    public function setPendingTime($val) {
+      if (is_double($val)) {
+        $this->pending_time = sprintf('%0.0F', $val);
+      } else {
+        $this->pending_time = $val;
+      }
+      return $this;
+    }
+    public function clearPendingTime() {
+      unset($this->pending_time);
+      return $this;
+    }
+    public function hasPendingTime() {
+      return isset($this->pending_time);
+    }
+    public function getReplicaIndex() {
+      if (!isset($this->replica_index)) {
+        return -1;
+      }
+      return $this->replica_index;
+    }
+    public function setReplicaIndex($val) {
+      $this->replica_index = $val;
+      return $this;
+    }
+    public function clearReplicaIndex() {
+      unset($this->replica_index);
+      return $this;
+    }
+    public function hasReplicaIndex() {
+      return isset($this->replica_index);
+    }
+    public function getFinished() {
+      if (!isset($this->finished)) {
+        return true;
+      }
+      return $this->finished;
+    }
+    public function setFinished($val) {
+      $this->finished = $val;
+      return $this;
+    }
+    public function clearFinished() {
+      unset($this->finished);
+      return $this;
+    }
+    public function hasFinished() {
+      return isset($this->finished);
+    }
+    public function getCloneKey() {
+      if (!isset($this->clone_key)) {
+        return '';
+      }
+      return $this->clone_key;
+    }
+    public function setCloneKey($val) {
+      $this->clone_key = $val;
+      return $this;
+    }
+    public function clearCloneKey() {
+      unset($this->clone_key);
+      return $this;
+    }
+    public function hasCloneKey() {
+      return isset($this->clone_key);
+    }
+    public function getLineSize() {
+      return sizeof($this->line);
+    }
+    public function getLineList() {
+      return $this->line;
+    }
+    public function mutableLine($idx) {
+      if (!isset($this->line[$idx])) {
+        $val = new \google\appengine\LogLine();
+        $this->line[$idx] = $val;
+        return $val;
+      }
+      return $this->line[$idx];
+    }
+    public function getLine($idx) {
+      if (isset($this->line[$idx])) {
+        return $this->line[$idx];
+      }
+      if ($idx >= end(array_keys($this->line))) {
+        throw new \OutOfRangeException('index out of range: ' + $idx);
+      }
+      return new \google\appengine\LogLine();
+    }
+    public function addLine() {
+      $val = new \google\appengine\LogLine();
+      $this->line[] = $val;
+      return $val;
+    }
+    public function clearLine() {
+      $this->line = array();
+    }
+    public function getExitReason() {
+      if (!isset($this->exit_reason)) {
+        return 0;
+      }
+      return $this->exit_reason;
+    }
+    public function setExitReason($val) {
+      $this->exit_reason = $val;
+      return $this;
+    }
+    public function clearExitReason() {
+      unset($this->exit_reason);
+      return $this;
+    }
+    public function hasExitReason() {
+      return isset($this->exit_reason);
+    }
+    public function getWasThrottledForTime() {
+      if (!isset($this->was_throttled_for_time)) {
+        return false;
+      }
+      return $this->was_throttled_for_time;
+    }
+    public function setWasThrottledForTime($val) {
+      $this->was_throttled_for_time = $val;
+      return $this;
+    }
+    public function clearWasThrottledForTime() {
+      unset($this->was_throttled_for_time);
+      return $this;
+    }
+    public function hasWasThrottledForTime() {
+      return isset($this->was_throttled_for_time);
+    }
+    public function getWasThrottledForRequests() {
+      if (!isset($this->was_throttled_for_requests)) {
+        return false;
+      }
+      return $this->was_throttled_for_requests;
+    }
+    public function setWasThrottledForRequests($val) {
+      $this->was_throttled_for_requests = $val;
+      return $this;
+    }
+    public function clearWasThrottledForRequests() {
+      unset($this->was_throttled_for_requests);
+      return $this;
+    }
+    public function hasWasThrottledForRequests() {
+      return isset($this->was_throttled_for_requests);
+    }
+    public function getThrottledTime() {
+      if (!isset($this->throttled_time)) {
+        return "0";
+      }
+      return $this->throttled_time;
+    }
+    public function setThrottledTime($val) {
+      if (is_double($val)) {
+        $this->throttled_time = sprintf('%0.0F', $val);
+      } else {
+        $this->throttled_time = $val;
+      }
+      return $this;
+    }
+    public function clearThrottledTime() {
+      unset($this->throttled_time);
+      return $this;
+    }
+    public function hasThrottledTime() {
+      return isset($this->throttled_time);
+    }
+    public function getServerName() {
+      if (!isset($this->server_name)) {
+        return '';
+      }
+      return $this->server_name;
+    }
+    public function setServerName($val) {
+      $this->server_name = $val;
+      return $this;
+    }
+    public function clearServerName() {
+      unset($this->server_name);
+      return $this;
+    }
+    public function hasServerName() {
+      return isset($this->server_name);
+    }
+    public function getOffset() {
+      if (!isset($this->offset)) {
+        return new \google\appengine\LogOffset();
+      }
+      return $this->offset;
+    }
+    public function mutableOffset() {
+      if (!isset($this->offset)) {
+        $res = new \google\appengine\LogOffset();
+        $this->offset = $res;
+        return $res;
+      }
+      return $this->offset;
+    }
+    public function clearOffset() {
+      if (isset($this->offset)) {
+        unset($this->offset);
+      }
+    }
+    public function hasOffset() {
+      return isset($this->offset);
+    }
+    public function getLinesIncomplete() {
+      if (!isset($this->lines_incomplete)) {
+        return false;
+      }
+      return $this->lines_incomplete;
+    }
+    public function setLinesIncomplete($val) {
+      $this->lines_incomplete = $val;
+      return $this;
+    }
+    public function clearLinesIncomplete() {
+      unset($this->lines_incomplete);
+      return $this;
+    }
+    public function hasLinesIncomplete() {
+      return isset($this->lines_incomplete);
+    }
+    public function getModuleId() {
+      if (!isset($this->module_id)) {
+        return "default";
+      }
+      return $this->module_id;
+    }
+    public function setModuleId($val) {
+      $this->module_id = $val;
+      return $this;
+    }
+    public function clearModuleId() {
+      unset($this->module_id);
+      return $this;
+    }
+    public function hasModuleId() {
+      return isset($this->module_id);
+    }
+    public function getAppEngineRelease() {
+      if (!isset($this->app_engine_release)) {
+        return '';
+      }
+      return $this->app_engine_release;
+    }
+    public function setAppEngineRelease($val) {
+      $this->app_engine_release = $val;
+      return $this;
+    }
+    public function clearAppEngineRelease() {
+      unset($this->app_engine_release);
+      return $this;
+    }
+    public function hasAppEngineRelease() {
+      return isset($this->app_engine_release);
+    }
+    public function clear() {
+      $this->clearAppId();
+      $this->clearVersionId();
+      $this->clearRequestId();
+      $this->clearIp();
+      $this->clearNickname();
+      $this->clearStartTime();
+      $this->clearEndTime();
+      $this->clearLatency();
+      $this->clearMcycles();
+      $this->clearMethod();
+      $this->clearResource();
+      $this->clearHttpVersion();
+      $this->clearStatus();
+      $this->clearResponseSize();
+      $this->clearReferrer();
+      $this->clearUserAgent();
+      $this->clearUrlMapEntry();
+      $this->clearCombined();
+      $this->clearApiMcycles();
+      $this->clearHost();
+      $this->clearCost();
+      $this->clearTaskQueueName();
+      $this->clearTaskName();
+      $this->clearWasLoadingRequest();
+      $this->clearPendingTime();
+      $this->clearReplicaIndex();
+      $this->clearFinished();
+      $this->clearCloneKey();
+      $this->clearLine();
+      $this->clearExitReason();
+      $this->clearWasThrottledForTime();
+      $this->clearWasThrottledForRequests();
+      $this->clearThrottledTime();
+      $this->clearServerName();
+      $this->clearOffset();
+      $this->clearLinesIncomplete();
+      $this->clearModuleId();
+      $this->clearAppEngineRelease();
+    }
+    public function byteSizePartial() {
+      $res = 0;
+      if (isset($this->app_id)) {
+        $res += 1;
+        $res += $this->lengthString(strlen($this->app_id));
+      }
+      if (isset($this->version_id)) {
+        $res += 1;
+        $res += $this->lengthString(strlen($this->version_id));
+      }
+      if (isset($this->request_id)) {
+        $res += 1;
+        $res += $this->lengthString(strlen($this->request_id));
+      }
+      if (isset($this->ip)) {
+        $res += 1;
+        $res += $this->lengthString(strlen($this->ip));
+      }
+      if (isset($this->nickname)) {
+        $res += 1;
+        $res += $this->lengthString(strlen($this->nickname));
+      }
+      if (isset($this->start_time)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->start_time);
+      }
+      if (isset($this->end_time)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->end_time);
+      }
+      if (isset($this->latency)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->latency);
+      }
+      if (isset($this->mcycles)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->mcycles);
+      }
+      if (isset($this->method)) {
+        $res += 1;
+        $res += $this->lengthString(strlen($this->method));
+      }
+      if (isset($this->resource)) {
+        $res += 1;
+        $res += $this->lengthString(strlen($this->resource));
+      }
+      if (isset($this->http_version)) {
+        $res += 1;
+        $res += $this->lengthString(strlen($this->http_version));
+      }
+      if (isset($this->status)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->status);
+      }
+      if (isset($this->response_size)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->response_size);
+      }
+      if (isset($this->referrer)) {
+        $res += 1;
+        $res += $this->lengthString(strlen($this->referrer));
+      }
+      if (isset($this->user_agent)) {
+        $res += 2;
+        $res += $this->lengthString(strlen($this->user_agent));
+      }
+      if (isset($this->url_map_entry)) {
+        $res += 2;
+        $res += $this->lengthString(strlen($this->url_map_entry));
+      }
+      if (isset($this->combined)) {
+        $res += 2;
+        $res += $this->lengthString(strlen($this->combined));
+      }
+      if (isset($this->api_mcycles)) {
+        $res += 2;
+        $res += $this->lengthVarInt64($this->api_mcycles);
+      }
+      if (isset($this->host)) {
+        $res += 2;
+        $res += $this->lengthString(strlen($this->host));
+      }
+      if (isset($this->cost)) {
+        $res += 10;
+      }
+      if (isset($this->task_queue_name)) {
+        $res += 2;
+        $res += $this->lengthString(strlen($this->task_queue_name));
+      }
+      if (isset($this->task_name)) {
+        $res += 2;
+        $res += $this->lengthString(strlen($this->task_name));
+      }
+      if (isset($this->was_loading_request)) {
+        $res += 3;
+      }
+      if (isset($this->pending_time)) {
+        $res += 2;
+        $res += $this->lengthVarInt64($this->pending_time);
+      }
+      if (isset($this->replica_index)) {
+        $res += 2;
+        $res += $this->lengthVarInt64($this->replica_index);
+      }
+      if (isset($this->finished)) {
+        $res += 3;
+      }
+      if (isset($this->clone_key)) {
+        $res += 2;
+        $res += $this->lengthString(strlen($this->clone_key));
+      }
+      $this->checkProtoArray($this->line);
+      $res += 2 * sizeof($this->line);
+      foreach ($this->line as $value) {
+        $res += $this->lengthString($value->byteSizePartial());
+      }
+      if (isset($this->exit_reason)) {
+        $res += 2;
+        $res += $this->lengthVarInt64($this->exit_reason);
+      }
+      if (isset($this->was_throttled_for_time)) {
+        $res += 3;
+      }
+      if (isset($this->was_throttled_for_requests)) {
+        $res += 3;
+      }
+      if (isset($this->throttled_time)) {
+        $res += 2;
+        $res += $this->lengthVarInt64($this->throttled_time);
+      }
+      if (isset($this->server_name)) {
+        $res += 2;
+        $res += $this->lengthString(strlen($this->server_name));
+      }
+      if (isset($this->offset)) {
+        $res += 2;
+        $res += $this->lengthString($this->offset->byteSizePartial());
+      }
+      if (isset($this->lines_incomplete)) {
+        $res += 3;
+      }
+      if (isset($this->module_id)) {
+        $res += 2;
+        $res += $this->lengthString(strlen($this->module_id));
+      }
+      if (isset($this->app_engine_release)) {
+        $res += 2;
+        $res += $this->lengthString(strlen($this->app_engine_release));
+      }
+      return $res;
+    }
+    public function outputPartial($out) {
+      if (isset($this->app_id)) {
+        $out->putVarInt32(10);
+        $out->putPrefixedString($this->app_id);
+      }
+      if (isset($this->version_id)) {
+        $out->putVarInt32(18);
+        $out->putPrefixedString($this->version_id);
+      }
+      if (isset($this->request_id)) {
+        $out->putVarInt32(26);
+        $out->putPrefixedString($this->request_id);
+      }
+      if (isset($this->ip)) {
+        $out->putVarInt32(34);
+        $out->putPrefixedString($this->ip);
+      }
+      if (isset($this->nickname)) {
+        $out->putVarInt32(42);
+        $out->putPrefixedString($this->nickname);
+      }
+      if (isset($this->start_time)) {
+        $out->putVarInt32(48);
+        $out->putVarInt64($this->start_time);
+      }
+      if (isset($this->end_time)) {
+        $out->putVarInt32(56);
+        $out->putVarInt64($this->end_time);
+      }
+      if (isset($this->latency)) {
+        $out->putVarInt32(64);
+        $out->putVarInt64($this->latency);
+      }
+      if (isset($this->mcycles)) {
+        $out->putVarInt32(72);
+        $out->putVarInt64($this->mcycles);
+      }
+      if (isset($this->method)) {
+        $out->putVarInt32(82);
+        $out->putPrefixedString($this->method);
+      }
+      if (isset($this->resource)) {
+        $out->putVarInt32(90);
+        $out->putPrefixedString($this->resource);
+      }
+      if (isset($this->http_version)) {
+        $out->putVarInt32(98);
+        $out->putPrefixedString($this->http_version);
+      }
+      if (isset($this->status)) {
+        $out->putVarInt32(104);
+        $out->putVarInt32($this->status);
+      }
+      if (isset($this->response_size)) {
+        $out->putVarInt32(112);
+        $out->putVarInt64($this->response_size);
+      }
+      if (isset($this->referrer)) {
+        $out->putVarInt32(122);
+        $out->putPrefixedString($this->referrer);
+      }
+      if (isset($this->user_agent)) {
+        $out->putVarInt32(130);
+        $out->putPrefixedString($this->user_agent);
+      }
+      if (isset($this->url_map_entry)) {
+        $out->putVarInt32(138);
+        $out->putPrefixedString($this->url_map_entry);
+      }
+      if (isset($this->combined)) {
+        $out->putVarInt32(146);
+        $out->putPrefixedString($this->combined);
+      }
+      if (isset($this->api_mcycles)) {
+        $out->putVarInt32(152);
+        $out->putVarInt64($this->api_mcycles);
+      }
+      if (isset($this->host)) {
+        $out->putVarInt32(162);
+        $out->putPrefixedString($this->host);
+      }
+      if (isset($this->cost)) {
+        $out->putVarInt32(169);
+        $out->putDouble($this->cost);
+      }
+      if (isset($this->task_queue_name)) {
+        $out->putVarInt32(178);
+        $out->putPrefixedString($this->task_queue_name);
+      }
+      if (isset($this->task_name)) {
+        $out->putVarInt32(186);
+        $out->putPrefixedString($this->task_name);
+      }
+      if (isset($this->was_loading_request)) {
+        $out->putVarInt32(192);
+        $out->putBoolean($this->was_loading_request);
+      }
+      if (isset($this->pending_time)) {
+        $out->putVarInt32(200);
+        $out->putVarInt64($this->pending_time);
+      }
+      if (isset($this->replica_index)) {
+        $out->putVarInt32(208);
+        $out->putVarInt32($this->replica_index);
+      }
+      if (isset($this->finished)) {
+        $out->putVarInt32(216);
+        $out->putBoolean($this->finished);
+      }
+      if (isset($this->clone_key)) {
+        $out->putVarInt32(226);
+        $out->putPrefixedString($this->clone_key);
+      }
+      $this->checkProtoArray($this->line);
+      foreach ($this->line as $value) {
+        $out->putVarInt32(234);
+        $out->putVarInt32($value->byteSizePartial());
+        $value->outputPartial($out);
+      }
+      if (isset($this->exit_reason)) {
+        $out->putVarInt32(240);
+        $out->putVarInt32($this->exit_reason);
+      }
+      if (isset($this->was_throttled_for_time)) {
+        $out->putVarInt32(248);
+        $out->putBoolean($this->was_throttled_for_time);
+      }
+      if (isset($this->was_throttled_for_requests)) {
+        $out->putVarInt32(256);
+        $out->putBoolean($this->was_throttled_for_requests);
+      }
+      if (isset($this->throttled_time)) {
+        $out->putVarInt32(264);
+        $out->putVarInt64($this->throttled_time);
+      }
+      if (isset($this->server_name)) {
+        $out->putVarInt32(274);
+        $out->putPrefixedString($this->server_name);
+      }
+      if (isset($this->offset)) {
+        $out->putVarInt32(282);
+        $out->putVarInt32($this->offset->byteSizePartial());
+        $this->offset->outputPartial($out);
+      }
+      if (isset($this->lines_incomplete)) {
+        $out->putVarInt32(288);
+        $out->putBoolean($this->lines_incomplete);
+      }
+      if (isset($this->module_id)) {
+        $out->putVarInt32(298);
+        $out->putPrefixedString($this->module_id);
+      }
+      if (isset($this->app_engine_release)) {
+        $out->putVarInt32(306);
+        $out->putPrefixedString($this->app_engine_release);
+      }
+    }
+    public function tryMerge($d) {
+      while($d->avail() > 0) {
+        $tt = $d->getVarInt32();
+        switch ($tt) {
+          case 10:
+            $length = $d->getVarInt32();
+            $this->setAppId(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 18:
+            $length = $d->getVarInt32();
+            $this->setVersionId(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 26:
+            $length = $d->getVarInt32();
+            $this->setRequestId(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 34:
+            $length = $d->getVarInt32();
+            $this->setIp(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 42:
+            $length = $d->getVarInt32();
+            $this->setNickname(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 48:
+            $this->setStartTime($d->getVarInt64());
+            break;
+          case 56:
+            $this->setEndTime($d->getVarInt64());
+            break;
+          case 64:
+            $this->setLatency($d->getVarInt64());
+            break;
+          case 72:
+            $this->setMcycles($d->getVarInt64());
+            break;
+          case 82:
+            $length = $d->getVarInt32();
+            $this->setMethod(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 90:
+            $length = $d->getVarInt32();
+            $this->setResource(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 98:
+            $length = $d->getVarInt32();
+            $this->setHttpVersion(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 104:
+            $this->setStatus($d->getVarInt32());
+            break;
+          case 112:
+            $this->setResponseSize($d->getVarInt64());
+            break;
+          case 122:
+            $length = $d->getVarInt32();
+            $this->setReferrer(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 130:
+            $length = $d->getVarInt32();
+            $this->setUserAgent(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 138:
+            $length = $d->getVarInt32();
+            $this->setUrlMapEntry(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 146:
+            $length = $d->getVarInt32();
+            $this->setCombined(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 152:
+            $this->setApiMcycles($d->getVarInt64());
+            break;
+          case 162:
+            $length = $d->getVarInt32();
+            $this->setHost(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 169:
+            $this->setCost($d->getDouble());
+            break;
+          case 178:
+            $length = $d->getVarInt32();
+            $this->setTaskQueueName(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 186:
+            $length = $d->getVarInt32();
+            $this->setTaskName(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 192:
+            $this->setWasLoadingRequest($d->getBoolean());
+            break;
+          case 200:
+            $this->setPendingTime($d->getVarInt64());
+            break;
+          case 208:
+            $this->setReplicaIndex($d->getVarInt32());
+            break;
+          case 216:
+            $this->setFinished($d->getBoolean());
+            break;
+          case 226:
+            $length = $d->getVarInt32();
+            $this->setCloneKey(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 234:
+            $length = $d->getVarInt32();
+            $tmp = new \google\net\Decoder($d->buffer(), $d->pos(), $d->pos() + $length);
+            $d->skip($length);
+            $this->addLine()->tryMerge($tmp);
+            break;
+          case 240:
+            $this->setExitReason($d->getVarInt32());
+            break;
+          case 248:
+            $this->setWasThrottledForTime($d->getBoolean());
+            break;
+          case 256:
+            $this->setWasThrottledForRequests($d->getBoolean());
+            break;
+          case 264:
+            $this->setThrottledTime($d->getVarInt64());
+            break;
+          case 274:
+            $length = $d->getVarInt32();
+            $this->setServerName(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 282:
+            $length = $d->getVarInt32();
+            $tmp = new \google\net\Decoder($d->buffer(), $d->pos(), $d->pos() + $length);
+            $d->skip($length);
+            $this->mutableOffset()->tryMerge($tmp);
+            break;
+          case 288:
+            $this->setLinesIncomplete($d->getBoolean());
+            break;
+          case 298:
+            $length = $d->getVarInt32();
+            $this->setModuleId(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 306:
+            $length = $d->getVarInt32();
+            $this->setAppEngineRelease(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 0:
+            throw new \google\net\ProtocolBufferDecodeError();
+            break;
+          default:
+            $d->skipData($tt);
+        }
+      };
+    }
+    public function checkInitialized() {
+      if (!isset($this->app_id)) return 'app_id';
+      if (!isset($this->version_id)) return 'version_id';
+      if (!isset($this->request_id)) return 'request_id';
+      if (!isset($this->ip)) return 'ip';
+      if (!isset($this->start_time)) return 'start_time';
+      if (!isset($this->end_time)) return 'end_time';
+      if (!isset($this->latency)) return 'latency';
+      if (!isset($this->mcycles)) return 'mcycles';
+      if (!isset($this->method)) return 'method';
+      if (!isset($this->resource)) return 'resource';
+      if (!isset($this->http_version)) return 'http_version';
+      if (!isset($this->status)) return 'status';
+      if (!isset($this->response_size)) return 'response_size';
+      if (!isset($this->url_map_entry)) return 'url_map_entry';
+      if (!isset($this->combined)) return 'combined';
+      foreach ($this->line as $value) {
+        if (!$value->isInitialized()) return 'line';
+      }
+      if (isset($this->offset) && (!$this->offset->isInitialized())) return 'offset';
+      return null;
+    }
+    public function mergeFrom($x) {
+      if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); }
+      if ($x->hasAppId()) {
+        $this->setAppId($x->getAppId());
+      }
+      if ($x->hasVersionId()) {
+        $this->setVersionId($x->getVersionId());
+      }
+      if ($x->hasRequestId()) {
+        $this->setRequestId($x->getRequestId());
+      }
+      if ($x->hasIp()) {
+        $this->setIp($x->getIp());
+      }
+      if ($x->hasNickname()) {
+        $this->setNickname($x->getNickname());
+      }
+      if ($x->hasStartTime()) {
+        $this->setStartTime($x->getStartTime());
+      }
+      if ($x->hasEndTime()) {
+        $this->setEndTime($x->getEndTime());
+      }
+      if ($x->hasLatency()) {
+        $this->setLatency($x->getLatency());
+      }
+      if ($x->hasMcycles()) {
+        $this->setMcycles($x->getMcycles());
+      }
+      if ($x->hasMethod()) {
+        $this->setMethod($x->getMethod());
+      }
+      if ($x->hasResource()) {
+        $this->setResource($x->getResource());
+      }
+      if ($x->hasHttpVersion()) {
+        $this->setHttpVersion($x->getHttpVersion());
+      }
+      if ($x->hasStatus()) {
+        $this->setStatus($x->getStatus());
+      }
+      if ($x->hasResponseSize()) {
+        $this->setResponseSize($x->getResponseSize());
+      }
+      if ($x->hasReferrer()) {
+        $this->setReferrer($x->getReferrer());
+      }
+      if ($x->hasUserAgent()) {
+        $this->setUserAgent($x->getUserAgent());
+      }
+      if ($x->hasUrlMapEntry()) {
+        $this->setUrlMapEntry($x->getUrlMapEntry());
+      }
+      if ($x->hasCombined()) {
+        $this->setCombined($x->getCombined());
+      }
+      if ($x->hasApiMcycles()) {
+        $this->setApiMcycles($x->getApiMcycles());
+      }
+      if ($x->hasHost()) {
+        $this->setHost($x->getHost());
+      }
+      if ($x->hasCost()) {
+        $this->setCost($x->getCost());
+      }
+      if ($x->hasTaskQueueName()) {
+        $this->setTaskQueueName($x->getTaskQueueName());
+      }
+      if ($x->hasTaskName()) {
+        $this->setTaskName($x->getTaskName());
+      }
+      if ($x->hasWasLoadingRequest()) {
+        $this->setWasLoadingRequest($x->getWasLoadingRequest());
+      }
+      if ($x->hasPendingTime()) {
+        $this->setPendingTime($x->getPendingTime());
+      }
+      if ($x->hasReplicaIndex()) {
+        $this->setReplicaIndex($x->getReplicaIndex());
+      }
+      if ($x->hasFinished()) {
+        $this->setFinished($x->getFinished());
+      }
+      if ($x->hasCloneKey()) {
+        $this->setCloneKey($x->getCloneKey());
+      }
+      foreach ($x->getLineList() as $v) {
+        $this->addLine()->copyFrom($v);
+      }
+      if ($x->hasExitReason()) {
+        $this->setExitReason($x->getExitReason());
+      }
+      if ($x->hasWasThrottledForTime()) {
+        $this->setWasThrottledForTime($x->getWasThrottledForTime());
+      }
+      if ($x->hasWasThrottledForRequests()) {
+        $this->setWasThrottledForRequests($x->getWasThrottledForRequests());
+      }
+      if ($x->hasThrottledTime()) {
+        $this->setThrottledTime($x->getThrottledTime());
+      }
+      if ($x->hasServerName()) {
+        $this->setServerName($x->getServerName());
+      }
+      if ($x->hasOffset()) {
+        $this->mutableOffset()->mergeFrom($x->getOffset());
+      }
+      if ($x->hasLinesIncomplete()) {
+        $this->setLinesIncomplete($x->getLinesIncomplete());
+      }
+      if ($x->hasModuleId()) {
+        $this->setModuleId($x->getModuleId());
+      }
+      if ($x->hasAppEngineRelease()) {
+        $this->setAppEngineRelease($x->getAppEngineRelease());
+      }
+    }
+    public function equals($x) {
+      if ($x === $this) { return true; }
+      if (isset($this->app_id) !== isset($x->app_id)) return false;
+      if (isset($this->app_id) && $this->app_id !== $x->app_id) return false;
+      if (isset($this->version_id) !== isset($x->version_id)) return false;
+      if (isset($this->version_id) && $this->version_id !== $x->version_id) return false;
+      if (isset($this->request_id) !== isset($x->request_id)) return false;
+      if (isset($this->request_id) && $this->request_id !== $x->request_id) return false;
+      if (isset($this->ip) !== isset($x->ip)) return false;
+      if (isset($this->ip) && $this->ip !== $x->ip) return false;
+      if (isset($this->nickname) !== isset($x->nickname)) return false;
+      if (isset($this->nickname) && $this->nickname !== $x->nickname) return false;
+      if (isset($this->start_time) !== isset($x->start_time)) return false;
+      if (isset($this->start_time) && !$this->integerEquals($this->start_time, $x->start_time)) return false;
+      if (isset($this->end_time) !== isset($x->end_time)) return false;
+      if (isset($this->end_time) && !$this->integerEquals($this->end_time, $x->end_time)) return false;
+      if (isset($this->latency) !== isset($x->latency)) return false;
+      if (isset($this->latency) && !$this->integerEquals($this->latency, $x->latency)) return false;
+      if (isset($this->mcycles) !== isset($x->mcycles)) return false;
+      if (isset($this->mcycles) && !$this->integerEquals($this->mcycles, $x->mcycles)) return false;
+      if (isset($this->method) !== isset($x->method)) return false;
+      if (isset($this->method) && $this->method !== $x->method) return false;
+      if (isset($this->resource) !== isset($x->resource)) return false;
+      if (isset($this->resource) && $this->resource !== $x->resource) return false;
+      if (isset($this->http_version) !== isset($x->http_version)) return false;
+      if (isset($this->http_version) && $this->http_version !== $x->http_version) return false;
+      if (isset($this->status) !== isset($x->status)) return false;
+      if (isset($this->status) && !$this->integerEquals($this->status, $x->status)) return false;
+      if (isset($this->response_size) !== isset($x->response_size)) return false;
+      if (isset($this->response_size) && !$this->integerEquals($this->response_size, $x->response_size)) return false;
+      if (isset($this->referrer) !== isset($x->referrer)) return false;
+      if (isset($this->referrer) && $this->referrer !== $x->referrer) return false;
+      if (isset($this->user_agent) !== isset($x->user_agent)) return false;
+      if (isset($this->user_agent) && $this->user_agent !== $x->user_agent) return false;
+      if (isset($this->url_map_entry) !== isset($x->url_map_entry)) return false;
+      if (isset($this->url_map_entry) && $this->url_map_entry !== $x->url_map_entry) return false;
+      if (isset($this->combined) !== isset($x->combined)) return false;
+      if (isset($this->combined) && $this->combined !== $x->combined) return false;
+      if (isset($this->api_mcycles) !== isset($x->api_mcycles)) return false;
+      if (isset($this->api_mcycles) && !$this->integerEquals($this->api_mcycles, $x->api_mcycles)) return false;
+      if (isset($this->host) !== isset($x->host)) return false;
+      if (isset($this->host) && $this->host !== $x->host) return false;
+      if (isset($this->cost) !== isset($x->cost)) return false;
+      if (isset($this->cost) && $this->cost !== $x->cost) return false;
+      if (isset($this->task_queue_name) !== isset($x->task_queue_name)) return false;
+      if (isset($this->task_queue_name) && $this->task_queue_name !== $x->task_queue_name) return false;
+      if (isset($this->task_name) !== isset($x->task_name)) return false;
+      if (isset($this->task_name) && $this->task_name !== $x->task_name) return false;
+      if (isset($this->was_loading_request) !== isset($x->was_loading_request)) return false;
+      if (isset($this->was_loading_request) && $this->was_loading_request !== $x->was_loading_request) return false;
+      if (isset($this->pending_time) !== isset($x->pending_time)) return false;
+      if (isset($this->pending_time) && !$this->integerEquals($this->pending_time, $x->pending_time)) return false;
+      if (isset($this->replica_index) !== isset($x->replica_index)) return false;
+      if (isset($this->replica_index) && !$this->integerEquals($this->replica_index, $x->replica_index)) return false;
+      if (isset($this->finished) !== isset($x->finished)) return false;
+      if (isset($this->finished) && $this->finished !== $x->finished) return false;
+      if (isset($this->clone_key) !== isset($x->clone_key)) return false;
+      if (isset($this->clone_key) && $this->clone_key !== $x->clone_key) return false;
+      if (sizeof($this->line) !== sizeof($x->line)) return false;
+      foreach (array_map(null, $this->line, $x->line) as $v) {
+        if (!$v[0]->equals($v[1])) return false;
+      }
+      if (isset($this->exit_reason) !== isset($x->exit_reason)) return false;
+      if (isset($this->exit_reason) && !$this->integerEquals($this->exit_reason, $x->exit_reason)) return false;
+      if (isset($this->was_throttled_for_time) !== isset($x->was_throttled_for_time)) return false;
+      if (isset($this->was_throttled_for_time) && $this->was_throttled_for_time !== $x->was_throttled_for_time) return false;
+      if (isset($this->was_throttled_for_requests) !== isset($x->was_throttled_for_requests)) return false;
+      if (isset($this->was_throttled_for_requests) && $this->was_throttled_for_requests !== $x->was_throttled_for_requests) return false;
+      if (isset($this->throttled_time) !== isset($x->throttled_time)) return false;
+      if (isset($this->throttled_time) && !$this->integerEquals($this->throttled_time, $x->throttled_time)) return false;
+      if (isset($this->server_name) !== isset($x->server_name)) return false;
+      if (isset($this->server_name) && $this->server_name !== $x->server_name) return false;
+      if (isset($this->offset) !== isset($x->offset)) return false;
+      if (isset($this->offset) && !$this->offset->equals($x->offset)) return false;
+      if (isset($this->lines_incomplete) !== isset($x->lines_incomplete)) return false;
+      if (isset($this->lines_incomplete) && $this->lines_incomplete !== $x->lines_incomplete) return false;
+      if (isset($this->module_id) !== isset($x->module_id)) return false;
+      if (isset($this->module_id) && $this->module_id !== $x->module_id) return false;
+      if (isset($this->app_engine_release) !== isset($x->app_engine_release)) return false;
+      if (isset($this->app_engine_release) && $this->app_engine_release !== $x->app_engine_release) return false;
+      return true;
+    }
+    public function shortDebugString($prefix = "") {
+      $res = '';
+      if (isset($this->app_id)) {
+        $res .= $prefix . "app_id: " . $this->debugFormatString($this->app_id) . "\n";
+      }
+      if (isset($this->version_id)) {
+        $res .= $prefix . "version_id: " . $this->debugFormatString($this->version_id) . "\n";
+      }
+      if (isset($this->request_id)) {
+        $res .= $prefix . "request_id: " . $this->debugFormatString($this->request_id) . "\n";
+      }
+      if (isset($this->ip)) {
+        $res .= $prefix . "ip: " . $this->debugFormatString($this->ip) . "\n";
+      }
+      if (isset($this->nickname)) {
+        $res .= $prefix . "nickname: " . $this->debugFormatString($this->nickname) . "\n";
+      }
+      if (isset($this->start_time)) {
+        $res .= $prefix . "start_time: " . $this->debugFormatInt64($this->start_time) . "\n";
+      }
+      if (isset($this->end_time)) {
+        $res .= $prefix . "end_time: " . $this->debugFormatInt64($this->end_time) . "\n";
+      }
+      if (isset($this->latency)) {
+        $res .= $prefix . "latency: " . $this->debugFormatInt64($this->latency) . "\n";
+      }
+      if (isset($this->mcycles)) {
+        $res .= $prefix . "mcycles: " . $this->debugFormatInt64($this->mcycles) . "\n";
+      }
+      if (isset($this->method)) {
+        $res .= $prefix . "method: " . $this->debugFormatString($this->method) . "\n";
+      }
+      if (isset($this->resource)) {
+        $res .= $prefix . "resource: " . $this->debugFormatString($this->resource) . "\n";
+      }
+      if (isset($this->http_version)) {
+        $res .= $prefix . "http_version: " . $this->debugFormatString($this->http_version) . "\n";
+      }
+      if (isset($this->status)) {
+        $res .= $prefix . "status: " . $this->debugFormatInt32($this->status) . "\n";
+      }
+      if (isset($this->response_size)) {
+        $res .= $prefix . "response_size: " . $this->debugFormatInt64($this->response_size) . "\n";
+      }
+      if (isset($this->referrer)) {
+        $res .= $prefix . "referrer: " . $this->debugFormatString($this->referrer) . "\n";
+      }
+      if (isset($this->user_agent)) {
+        $res .= $prefix . "user_agent: " . $this->debugFormatString($this->user_agent) . "\n";
+      }
+      if (isset($this->url_map_entry)) {
+        $res .= $prefix . "url_map_entry: " . $this->debugFormatString($this->url_map_entry) . "\n";
+      }
+      if (isset($this->combined)) {
+        $res .= $prefix . "combined: " . $this->debugFormatString($this->combined) . "\n";
+      }
+      if (isset($this->api_mcycles)) {
+        $res .= $prefix . "api_mcycles: " . $this->debugFormatInt64($this->api_mcycles) . "\n";
+      }
+      if (isset($this->host)) {
+        $res .= $prefix . "host: " . $this->debugFormatString($this->host) . "\n";
+      }
+      if (isset($this->cost)) {
+        $res .= $prefix . "cost: " . $this->debugFormatDouble($this->cost) . "\n";
+      }
+      if (isset($this->task_queue_name)) {
+        $res .= $prefix . "task_queue_name: " . $this->debugFormatString($this->task_queue_name) . "\n";
+      }
+      if (isset($this->task_name)) {
+        $res .= $prefix . "task_name: " . $this->debugFormatString($this->task_name) . "\n";
+      }
+      if (isset($this->was_loading_request)) {
+        $res .= $prefix . "was_loading_request: " . $this->debugFormatBool($this->was_loading_request) . "\n";
+      }
+      if (isset($this->pending_time)) {
+        $res .= $prefix . "pending_time: " . $this->debugFormatInt64($this->pending_time) . "\n";
+      }
+      if (isset($this->replica_index)) {
+        $res .= $prefix . "replica_index: " . $this->debugFormatInt32($this->replica_index) . "\n";
+      }
+      if (isset($this->finished)) {
+        $res .= $prefix . "finished: " . $this->debugFormatBool($this->finished) . "\n";
+      }
+      if (isset($this->clone_key)) {
+        $res .= $prefix . "clone_key: " . $this->debugFormatString($this->clone_key) . "\n";
+      }
+      foreach ($this->line as $value) {
+        $res .= $prefix . "line <\n" . $value->shortDebugString($prefix . "  ") . $prefix . ">\n";
+      }
+      if (isset($this->exit_reason)) {
+        $res .= $prefix . "exit_reason: " . $this->debugFormatInt32($this->exit_reason) . "\n";
+      }
+      if (isset($this->was_throttled_for_time)) {
+        $res .= $prefix . "was_throttled_for_time: " . $this->debugFormatBool($this->was_throttled_for_time) . "\n";
+      }
+      if (isset($this->was_throttled_for_requests)) {
+        $res .= $prefix . "was_throttled_for_requests: " . $this->debugFormatBool($this->was_throttled_for_requests) . "\n";
+      }
+      if (isset($this->throttled_time)) {
+        $res .= $prefix . "throttled_time: " . $this->debugFormatInt64($this->throttled_time) . "\n";
+      }
+      if (isset($this->server_name)) {
+        $res .= $prefix . "server_name: " . $this->debugFormatString($this->server_name) . "\n";
+      }
+      if (isset($this->offset)) {
+        $res .= $prefix . "offset <\n" . $this->offset->shortDebugString($prefix . "  ") . $prefix . ">\n";
+      }
+      if (isset($this->lines_incomplete)) {
+        $res .= $prefix . "lines_incomplete: " . $this->debugFormatBool($this->lines_incomplete) . "\n";
+      }
+      if (isset($this->module_id)) {
+        $res .= $prefix . "module_id: " . $this->debugFormatString($this->module_id) . "\n";
+      }
+      if (isset($this->app_engine_release)) {
+        $res .= $prefix . "app_engine_release: " . $this->debugFormatString($this->app_engine_release) . "\n";
+      }
+      return $res;
+    }
+  }
+}
+namespace google\appengine {
+  class LogModuleVersion extends \google\net\ProtocolMessage {
+    public function getModuleId() {
+      if (!isset($this->module_id)) {
+        return "default";
+      }
+      return $this->module_id;
+    }
+    public function setModuleId($val) {
+      $this->module_id = $val;
+      return $this;
+    }
+    public function clearModuleId() {
+      unset($this->module_id);
+      return $this;
+    }
+    public function hasModuleId() {
+      return isset($this->module_id);
+    }
+    public function getVersionId() {
+      if (!isset($this->version_id)) {
+        return '';
+      }
+      return $this->version_id;
+    }
+    public function setVersionId($val) {
+      $this->version_id = $val;
+      return $this;
+    }
+    public function clearVersionId() {
+      unset($this->version_id);
+      return $this;
+    }
+    public function hasVersionId() {
+      return isset($this->version_id);
+    }
+    public function clear() {
+      $this->clearModuleId();
+      $this->clearVersionId();
+    }
+    public function byteSizePartial() {
+      $res = 0;
+      if (isset($this->module_id)) {
+        $res += 1;
+        $res += $this->lengthString(strlen($this->module_id));
+      }
+      if (isset($this->version_id)) {
+        $res += 1;
+        $res += $this->lengthString(strlen($this->version_id));
+      }
+      return $res;
+    }
+    public function outputPartial($out) {
+      if (isset($this->module_id)) {
+        $out->putVarInt32(10);
+        $out->putPrefixedString($this->module_id);
+      }
+      if (isset($this->version_id)) {
+        $out->putVarInt32(18);
+        $out->putPrefixedString($this->version_id);
+      }
+    }
+    public function tryMerge($d) {
+      while($d->avail() > 0) {
+        $tt = $d->getVarInt32();
+        switch ($tt) {
+          case 10:
+            $length = $d->getVarInt32();
+            $this->setModuleId(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 18:
+            $length = $d->getVarInt32();
+            $this->setVersionId(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 0:
+            throw new \google\net\ProtocolBufferDecodeError();
+            break;
+          default:
+            $d->skipData($tt);
+        }
+      };
+    }
+    public function checkInitialized() {
+      return null;
+    }
+    public function mergeFrom($x) {
+      if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); }
+      if ($x->hasModuleId()) {
+        $this->setModuleId($x->getModuleId());
+      }
+      if ($x->hasVersionId()) {
+        $this->setVersionId($x->getVersionId());
+      }
+    }
+    public function equals($x) {
+      if ($x === $this) { return true; }
+      if (isset($this->module_id) !== isset($x->module_id)) return false;
+      if (isset($this->module_id) && $this->module_id !== $x->module_id) return false;
+      if (isset($this->version_id) !== isset($x->version_id)) return false;
+      if (isset($this->version_id) && $this->version_id !== $x->version_id) return false;
+      return true;
+    }
+    public function shortDebugString($prefix = "") {
+      $res = '';
+      if (isset($this->module_id)) {
+        $res .= $prefix . "module_id: " . $this->debugFormatString($this->module_id) . "\n";
+      }
+      if (isset($this->version_id)) {
+        $res .= $prefix . "version_id: " . $this->debugFormatString($this->version_id) . "\n";
+      }
+      return $res;
+    }
+  }
+}
+namespace google\appengine {
+  class LogReadRequest extends \google\net\ProtocolMessage {
+    private $version_id = array();
+    private $request_id = array();
+    private $module_version = array();
+    public function getAppId() {
+      if (!isset($this->app_id)) {
+        return '';
+      }
+      return $this->app_id;
+    }
+    public function setAppId($val) {
+      $this->app_id = $val;
+      return $this;
+    }
+    public function clearAppId() {
+      unset($this->app_id);
+      return $this;
+    }
+    public function hasAppId() {
+      return isset($this->app_id);
+    }
+    public function getVersionIdSize() {
+      return sizeof($this->version_id);
+    }
+    public function getVersionIdList() {
+      return $this->version_id;
+    }
+    public function getVersionId($idx) {
+      return $this->version_id[$idx];
+    }
+    public function setVersionId($idx, $val) {
+      $this->version_id[$idx] = $val;
+      return $this;
+    }
+    public function addVersionId($val) {
+      $this->version_id[] = $val;
+      return $this;
+    }
+    public function clearVersionId() {
+      $this->version_id = array();
+    }
+    public function getStartTime() {
+      if (!isset($this->start_time)) {
+        return "0";
+      }
+      return $this->start_time;
+    }
+    public function setStartTime($val) {
+      if (is_double($val)) {
+        $this->start_time = sprintf('%0.0F', $val);
+      } else {
+        $this->start_time = $val;
+      }
+      return $this;
+    }
+    public function clearStartTime() {
+      unset($this->start_time);
+      return $this;
+    }
+    public function hasStartTime() {
+      return isset($this->start_time);
+    }
+    public function getEndTime() {
+      if (!isset($this->end_time)) {
+        return "0";
+      }
+      return $this->end_time;
+    }
+    public function setEndTime($val) {
+      if (is_double($val)) {
+        $this->end_time = sprintf('%0.0F', $val);
+      } else {
+        $this->end_time = $val;
+      }
+      return $this;
+    }
+    public function clearEndTime() {
+      unset($this->end_time);
+      return $this;
+    }
+    public function hasEndTime() {
+      return isset($this->end_time);
+    }
+    public function getOffset() {
+      if (!isset($this->offset)) {
+        return new \google\appengine\LogOffset();
+      }
+      return $this->offset;
+    }
+    public function mutableOffset() {
+      if (!isset($this->offset)) {
+        $res = new \google\appengine\LogOffset();
+        $this->offset = $res;
+        return $res;
+      }
+      return $this->offset;
+    }
+    public function clearOffset() {
+      if (isset($this->offset)) {
+        unset($this->offset);
+      }
+    }
+    public function hasOffset() {
+      return isset($this->offset);
+    }
+    public function getRequestIdSize() {
+      return sizeof($this->request_id);
+    }
+    public function getRequestIdList() {
+      return $this->request_id;
+    }
+    public function getRequestId($idx) {
+      return $this->request_id[$idx];
+    }
+    public function setRequestId($idx, $val) {
+      $this->request_id[$idx] = $val;
+      return $this;
+    }
+    public function addRequestId($val) {
+      $this->request_id[] = $val;
+      return $this;
+    }
+    public function clearRequestId() {
+      $this->request_id = array();
+    }
+    public function getMinimumLogLevel() {
+      if (!isset($this->minimum_log_level)) {
+        return 0;
+      }
+      return $this->minimum_log_level;
+    }
+    public function setMinimumLogLevel($val) {
+      $this->minimum_log_level = $val;
+      return $this;
+    }
+    public function clearMinimumLogLevel() {
+      unset($this->minimum_log_level);
+      return $this;
+    }
+    public function hasMinimumLogLevel() {
+      return isset($this->minimum_log_level);
+    }
+    public function getIncludeIncomplete() {
+      if (!isset($this->include_incomplete)) {
+        return false;
+      }
+      return $this->include_incomplete;
+    }
+    public function setIncludeIncomplete($val) {
+      $this->include_incomplete = $val;
+      return $this;
+    }
+    public function clearIncludeIncomplete() {
+      unset($this->include_incomplete);
+      return $this;
+    }
+    public function hasIncludeIncomplete() {
+      return isset($this->include_incomplete);
+    }
+    public function getCount() {
+      if (!isset($this->count)) {
+        return "0";
+      }
+      return $this->count;
+    }
+    public function setCount($val) {
+      if (is_double($val)) {
+        $this->count = sprintf('%0.0F', $val);
+      } else {
+        $this->count = $val;
+      }
+      return $this;
+    }
+    public function clearCount() {
+      unset($this->count);
+      return $this;
+    }
+    public function hasCount() {
+      return isset($this->count);
+    }
+    public function getIncludeAppLogs() {
+      if (!isset($this->include_app_logs)) {
+        return false;
+      }
+      return $this->include_app_logs;
+    }
+    public function setIncludeAppLogs($val) {
+      $this->include_app_logs = $val;
+      return $this;
+    }
+    public function clearIncludeAppLogs() {
+      unset($this->include_app_logs);
+      return $this;
+    }
+    public function hasIncludeAppLogs() {
+      return isset($this->include_app_logs);
+    }
+    public function getIncludeHost() {
+      if (!isset($this->include_host)) {
+        return false;
+      }
+      return $this->include_host;
+    }
+    public function setIncludeHost($val) {
+      $this->include_host = $val;
+      return $this;
+    }
+    public function clearIncludeHost() {
+      unset($this->include_host);
+      return $this;
+    }
+    public function hasIncludeHost() {
+      return isset($this->include_host);
+    }
+    public function getIncludeAll() {
+      if (!isset($this->include_all)) {
+        return false;
+      }
+      return $this->include_all;
+    }
+    public function setIncludeAll($val) {
+      $this->include_all = $val;
+      return $this;
+    }
+    public function clearIncludeAll() {
+      unset($this->include_all);
+      return $this;
+    }
+    public function hasIncludeAll() {
+      return isset($this->include_all);
+    }
+    public function getCacheIterator() {
+      if (!isset($this->cache_iterator)) {
+        return false;
+      }
+      return $this->cache_iterator;
+    }
+    public function setCacheIterator($val) {
+      $this->cache_iterator = $val;
+      return $this;
+    }
+    public function clearCacheIterator() {
+      unset($this->cache_iterator);
+      return $this;
+    }
+    public function hasCacheIterator() {
+      return isset($this->cache_iterator);
+    }
+    public function getCombinedLogRegex() {
+      if (!isset($this->combined_log_regex)) {
+        return '';
+      }
+      return $this->combined_log_regex;
+    }
+    public function setCombinedLogRegex($val) {
+      $this->combined_log_regex = $val;
+      return $this;
+    }
+    public function clearCombinedLogRegex() {
+      unset($this->combined_log_regex);
+      return $this;
+    }
+    public function hasCombinedLogRegex() {
+      return isset($this->combined_log_regex);
+    }
+    public function getHostRegex() {
+      if (!isset($this->host_regex)) {
+        return '';
+      }
+      return $this->host_regex;
+    }
+    public function setHostRegex($val) {
+      $this->host_regex = $val;
+      return $this;
+    }
+    public function clearHostRegex() {
+      unset($this->host_regex);
+      return $this;
+    }
+    public function hasHostRegex() {
+      return isset($this->host_regex);
+    }
+    public function getReplicaIndex() {
+      if (!isset($this->replica_index)) {
+        return 0;
+      }
+      return $this->replica_index;
+    }
+    public function setReplicaIndex($val) {
+      $this->replica_index = $val;
+      return $this;
+    }
+    public function clearReplicaIndex() {
+      unset($this->replica_index);
+      return $this;
+    }
+    public function hasReplicaIndex() {
+      return isset($this->replica_index);
+    }
+    public function getAppLogsPerRequest() {
+      if (!isset($this->app_logs_per_request)) {
+        return 0;
+      }
+      return $this->app_logs_per_request;
+    }
+    public function setAppLogsPerRequest($val) {
+      $this->app_logs_per_request = $val;
+      return $this;
+    }
+    public function clearAppLogsPerRequest() {
+      unset($this->app_logs_per_request);
+      return $this;
+    }
+    public function hasAppLogsPerRequest() {
+      return isset($this->app_logs_per_request);
+    }
+    public function getNumShards() {
+      if (!isset($this->num_shards)) {
+        return 0;
+      }
+      return $this->num_shards;
+    }
+    public function setNumShards($val) {
+      $this->num_shards = $val;
+      return $this;
+    }
+    public function clearNumShards() {
+      unset($this->num_shards);
+      return $this;
+    }
+    public function hasNumShards() {
+      return isset($this->num_shards);
+    }
+    public function getModuleVersionSize() {
+      return sizeof($this->module_version);
+    }
+    public function getModuleVersionList() {
+      return $this->module_version;
+    }
+    public function mutableModuleVersion($idx) {
+      if (!isset($this->module_version[$idx])) {
+        $val = new \google\appengine\LogModuleVersion();
+        $this->module_version[$idx] = $val;
+        return $val;
+      }
+      return $this->module_version[$idx];
+    }
+    public function getModuleVersion($idx) {
+      if (isset($this->module_version[$idx])) {
+        return $this->module_version[$idx];
+      }
+      if ($idx >= end(array_keys($this->module_version))) {
+        throw new \OutOfRangeException('index out of range: ' + $idx);
+      }
+      return new \google\appengine\LogModuleVersion();
+    }
+    public function addModuleVersion() {
+      $val = new \google\appengine\LogModuleVersion();
+      $this->module_version[] = $val;
+      return $val;
+    }
+    public function clearModuleVersion() {
+      $this->module_version = array();
+    }
+    public function clear() {
+      $this->clearAppId();
+      $this->clearVersionId();
+      $this->clearStartTime();
+      $this->clearEndTime();
+      $this->clearOffset();
+      $this->clearRequestId();
+      $this->clearMinimumLogLevel();
+      $this->clearIncludeIncomplete();
+      $this->clearCount();
+      $this->clearIncludeAppLogs();
+      $this->clearIncludeHost();
+      $this->clearIncludeAll();
+      $this->clearCacheIterator();
+      $this->clearCombinedLogRegex();
+      $this->clearHostRegex();
+      $this->clearReplicaIndex();
+      $this->clearAppLogsPerRequest();
+      $this->clearNumShards();
+      $this->clearModuleVersion();
+    }
+    public function byteSizePartial() {
+      $res = 0;
+      if (isset($this->app_id)) {
+        $res += 1;
+        $res += $this->lengthString(strlen($this->app_id));
+      }
+      $this->checkProtoArray($this->version_id);
+      $res += 1 * sizeof($this->version_id);
+      foreach ($this->version_id as $value) {
+        $res += $this->lengthString(strlen($value));
+      }
+      if (isset($this->start_time)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->start_time);
+      }
+      if (isset($this->end_time)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->end_time);
+      }
+      if (isset($this->offset)) {
+        $res += 1;
+        $res += $this->lengthString($this->offset->byteSizePartial());
+      }
+      $this->checkProtoArray($this->request_id);
+      $res += 1 * sizeof($this->request_id);
+      foreach ($this->request_id as $value) {
+        $res += $this->lengthString(strlen($value));
+      }
+      if (isset($this->minimum_log_level)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->minimum_log_level);
+      }
+      if (isset($this->include_incomplete)) {
+        $res += 2;
+      }
+      if (isset($this->count)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->count);
+      }
+      if (isset($this->include_app_logs)) {
+        $res += 2;
+      }
+      if (isset($this->include_host)) {
+        $res += 2;
+      }
+      if (isset($this->include_all)) {
+        $res += 2;
+      }
+      if (isset($this->cache_iterator)) {
+        $res += 2;
+      }
+      if (isset($this->combined_log_regex)) {
+        $res += 1;
+        $res += $this->lengthString(strlen($this->combined_log_regex));
+      }
+      if (isset($this->host_regex)) {
+        $res += 1;
+        $res += $this->lengthString(strlen($this->host_regex));
+      }
+      if (isset($this->replica_index)) {
+        $res += 2;
+        $res += $this->lengthVarInt64($this->replica_index);
+      }
+      if (isset($this->app_logs_per_request)) {
+        $res += 2;
+        $res += $this->lengthVarInt64($this->app_logs_per_request);
+      }
+      if (isset($this->num_shards)) {
+        $res += 2;
+        $res += $this->lengthVarInt64($this->num_shards);
+      }
+      $this->checkProtoArray($this->module_version);
+      $res += 2 * sizeof($this->module_version);
+      foreach ($this->module_version as $value) {
+        $res += $this->lengthString($value->byteSizePartial());
+      }
+      return $res;
+    }
+    public function outputPartial($out) {
+      if (isset($this->app_id)) {
+        $out->putVarInt32(10);
+        $out->putPrefixedString($this->app_id);
+      }
+      $this->checkProtoArray($this->version_id);
+      foreach ($this->version_id as $value) {
+        $out->putVarInt32(18);
+        $out->putPrefixedString($value);
+      }
+      if (isset($this->start_time)) {
+        $out->putVarInt32(24);
+        $out->putVarInt64($this->start_time);
+      }
+      if (isset($this->end_time)) {
+        $out->putVarInt32(32);
+        $out->putVarInt64($this->end_time);
+      }
+      if (isset($this->offset)) {
+        $out->putVarInt32(42);
+        $out->putVarInt32($this->offset->byteSizePartial());
+        $this->offset->outputPartial($out);
+      }
+      $this->checkProtoArray($this->request_id);
+      foreach ($this->request_id as $value) {
+        $out->putVarInt32(50);
+        $out->putPrefixedString($value);
+      }
+      if (isset($this->minimum_log_level)) {
+        $out->putVarInt32(56);
+        $out->putVarInt32($this->minimum_log_level);
+      }
+      if (isset($this->include_incomplete)) {
+        $out->putVarInt32(64);
+        $out->putBoolean($this->include_incomplete);
+      }
+      if (isset($this->count)) {
+        $out->putVarInt32(72);
+        $out->putVarInt64($this->count);
+      }
+      if (isset($this->include_app_logs)) {
+        $out->putVarInt32(80);
+        $out->putBoolean($this->include_app_logs);
+      }
+      if (isset($this->include_host)) {
+        $out->putVarInt32(88);
+        $out->putBoolean($this->include_host);
+      }
+      if (isset($this->include_all)) {
+        $out->putVarInt32(96);
+        $out->putBoolean($this->include_all);
+      }
+      if (isset($this->cache_iterator)) {
+        $out->putVarInt32(104);
+        $out->putBoolean($this->cache_iterator);
+      }
+      if (isset($this->combined_log_regex)) {
+        $out->putVarInt32(114);
+        $out->putPrefixedString($this->combined_log_regex);
+      }
+      if (isset($this->host_regex)) {
+        $out->putVarInt32(122);
+        $out->putPrefixedString($this->host_regex);
+      }
+      if (isset($this->replica_index)) {
+        $out->putVarInt32(128);
+        $out->putVarInt32($this->replica_index);
+      }
+      if (isset($this->app_logs_per_request)) {
+        $out->putVarInt32(136);
+        $out->putVarInt32($this->app_logs_per_request);
+      }
+      if (isset($this->num_shards)) {
+        $out->putVarInt32(144);
+        $out->putVarInt32($this->num_shards);
+      }
+      $this->checkProtoArray($this->module_version);
+      foreach ($this->module_version as $value) {
+        $out->putVarInt32(154);
+        $out->putVarInt32($value->byteSizePartial());
+        $value->outputPartial($out);
+      }
+    }
+    public function tryMerge($d) {
+      while($d->avail() > 0) {
+        $tt = $d->getVarInt32();
+        switch ($tt) {
+          case 10:
+            $length = $d->getVarInt32();
+            $this->setAppId(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 18:
+            $length = $d->getVarInt32();
+            $this->addVersionId(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 24:
+            $this->setStartTime($d->getVarInt64());
+            break;
+          case 32:
+            $this->setEndTime($d->getVarInt64());
+            break;
+          case 42:
+            $length = $d->getVarInt32();
+            $tmp = new \google\net\Decoder($d->buffer(), $d->pos(), $d->pos() + $length);
+            $d->skip($length);
+            $this->mutableOffset()->tryMerge($tmp);
+            break;
+          case 50:
+            $length = $d->getVarInt32();
+            $this->addRequestId(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 56:
+            $this->setMinimumLogLevel($d->getVarInt32());
+            break;
+          case 64:
+            $this->setIncludeIncomplete($d->getBoolean());
+            break;
+          case 72:
+            $this->setCount($d->getVarInt64());
+            break;
+          case 80:
+            $this->setIncludeAppLogs($d->getBoolean());
+            break;
+          case 88:
+            $this->setIncludeHost($d->getBoolean());
+            break;
+          case 96:
+            $this->setIncludeAll($d->getBoolean());
+            break;
+          case 104:
+            $this->setCacheIterator($d->getBoolean());
+            break;
+          case 114:
+            $length = $d->getVarInt32();
+            $this->setCombinedLogRegex(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 122:
+            $length = $d->getVarInt32();
+            $this->setHostRegex(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 128:
+            $this->setReplicaIndex($d->getVarInt32());
+            break;
+          case 136:
+            $this->setAppLogsPerRequest($d->getVarInt32());
+            break;
+          case 144:
+            $this->setNumShards($d->getVarInt32());
+            break;
+          case 154:
+            $length = $d->getVarInt32();
+            $tmp = new \google\net\Decoder($d->buffer(), $d->pos(), $d->pos() + $length);
+            $d->skip($length);
+            $this->addModuleVersion()->tryMerge($tmp);
+            break;
+          case 0:
+            throw new \google\net\ProtocolBufferDecodeError();
+            break;
+          default:
+            $d->skipData($tt);
+        }
+      };
+    }
+    public function checkInitialized() {
+      if (!isset($this->app_id)) return 'app_id';
+      if (isset($this->offset) && (!$this->offset->isInitialized())) return 'offset';
+      foreach ($this->module_version as $value) {
+        if (!$value->isInitialized()) return 'module_version';
+      }
+      return null;
+    }
+    public function mergeFrom($x) {
+      if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); }
+      if ($x->hasAppId()) {
+        $this->setAppId($x->getAppId());
+      }
+      foreach ($x->getVersionIdList() as $v) {
+        $this->addVersionId($v);
+      }
+      if ($x->hasStartTime()) {
+        $this->setStartTime($x->getStartTime());
+      }
+      if ($x->hasEndTime()) {
+        $this->setEndTime($x->getEndTime());
+      }
+      if ($x->hasOffset()) {
+        $this->mutableOffset()->mergeFrom($x->getOffset());
+      }
+      foreach ($x->getRequestIdList() as $v) {
+        $this->addRequestId($v);
+      }
+      if ($x->hasMinimumLogLevel()) {
+        $this->setMinimumLogLevel($x->getMinimumLogLevel());
+      }
+      if ($x->hasIncludeIncomplete()) {
+        $this->setIncludeIncomplete($x->getIncludeIncomplete());
+      }
+      if ($x->hasCount()) {
+        $this->setCount($x->getCount());
+      }
+      if ($x->hasIncludeAppLogs()) {
+        $this->setIncludeAppLogs($x->getIncludeAppLogs());
+      }
+      if ($x->hasIncludeHost()) {
+        $this->setIncludeHost($x->getIncludeHost());
+      }
+      if ($x->hasIncludeAll()) {
+        $this->setIncludeAll($x->getIncludeAll());
+      }
+      if ($x->hasCacheIterator()) {
+        $this->setCacheIterator($x->getCacheIterator());
+      }
+      if ($x->hasCombinedLogRegex()) {
+        $this->setCombinedLogRegex($x->getCombinedLogRegex());
+      }
+      if ($x->hasHostRegex()) {
+        $this->setHostRegex($x->getHostRegex());
+      }
+      if ($x->hasReplicaIndex()) {
+        $this->setReplicaIndex($x->getReplicaIndex());
+      }
+      if ($x->hasAppLogsPerRequest()) {
+        $this->setAppLogsPerRequest($x->getAppLogsPerRequest());
+      }
+      if ($x->hasNumShards()) {
+        $this->setNumShards($x->getNumShards());
+      }
+      foreach ($x->getModuleVersionList() as $v) {
+        $this->addModuleVersion()->copyFrom($v);
+      }
+    }
+    public function equals($x) {
+      if ($x === $this) { return true; }
+      if (isset($this->app_id) !== isset($x->app_id)) return false;
+      if (isset($this->app_id) && $this->app_id !== $x->app_id) return false;
+      if (sizeof($this->version_id) !== sizeof($x->version_id)) return false;
+      foreach (array_map(null, $this->version_id, $x->version_id) as $v) {
+        if ($v[0] !== $v[1]) return false;
+      }
+      if (isset($this->start_time) !== isset($x->start_time)) return false;
+      if (isset($this->start_time) && !$this->integerEquals($this->start_time, $x->start_time)) return false;
+      if (isset($this->end_time) !== isset($x->end_time)) return false;
+      if (isset($this->end_time) && !$this->integerEquals($this->end_time, $x->end_time)) return false;
+      if (isset($this->offset) !== isset($x->offset)) return false;
+      if (isset($this->offset) && !$this->offset->equals($x->offset)) return false;
+      if (sizeof($this->request_id) !== sizeof($x->request_id)) return false;
+      foreach (array_map(null, $this->request_id, $x->request_id) as $v) {
+        if ($v[0] !== $v[1]) return false;
+      }
+      if (isset($this->minimum_log_level) !== isset($x->minimum_log_level)) return false;
+      if (isset($this->minimum_log_level) && !$this->integerEquals($this->minimum_log_level, $x->minimum_log_level)) return false;
+      if (isset($this->include_incomplete) !== isset($x->include_incomplete)) return false;
+      if (isset($this->include_incomplete) && $this->include_incomplete !== $x->include_incomplete) return false;
+      if (isset($this->count) !== isset($x->count)) return false;
+      if (isset($this->count) && !$this->integerEquals($this->count, $x->count)) return false;
+      if (isset($this->include_app_logs) !== isset($x->include_app_logs)) return false;
+      if (isset($this->include_app_logs) && $this->include_app_logs !== $x->include_app_logs) return false;
+      if (isset($this->include_host) !== isset($x->include_host)) return false;
+      if (isset($this->include_host) && $this->include_host !== $x->include_host) return false;
+      if (isset($this->include_all) !== isset($x->include_all)) return false;
+      if (isset($this->include_all) && $this->include_all !== $x->include_all) return false;
+      if (isset($this->cache_iterator) !== isset($x->cache_iterator)) return false;
+      if (isset($this->cache_iterator) && $this->cache_iterator !== $x->cache_iterator) return false;
+      if (isset($this->combined_log_regex) !== isset($x->combined_log_regex)) return false;
+      if (isset($this->combined_log_regex) && $this->combined_log_regex !== $x->combined_log_regex) return false;
+      if (isset($this->host_regex) !== isset($x->host_regex)) return false;
+      if (isset($this->host_regex) && $this->host_regex !== $x->host_regex) return false;
+      if (isset($this->replica_index) !== isset($x->replica_index)) return false;
+      if (isset($this->replica_index) && !$this->integerEquals($this->replica_index, $x->replica_index)) return false;
+      if (isset($this->app_logs_per_request) !== isset($x->app_logs_per_request)) return false;
+      if (isset($this->app_logs_per_request) && !$this->integerEquals($this->app_logs_per_request, $x->app_logs_per_request)) return false;
+      if (isset($this->num_shards) !== isset($x->num_shards)) return false;
+      if (isset($this->num_shards) && !$this->integerEquals($this->num_shards, $x->num_shards)) return false;
+      if (sizeof($this->module_version) !== sizeof($x->module_version)) return false;
+      foreach (array_map(null, $this->module_version, $x->module_version) as $v) {
+        if (!$v[0]->equals($v[1])) return false;
+      }
+      return true;
+    }
+    public function shortDebugString($prefix = "") {
+      $res = '';
+      if (isset($this->app_id)) {
+        $res .= $prefix . "app_id: " . $this->debugFormatString($this->app_id) . "\n";
+      }
+      foreach ($this->version_id as $value) {
+        $res .= $prefix . "version_id: " . $this->debugFormatString($value) . "\n";
+      }
+      if (isset($this->start_time)) {
+        $res .= $prefix . "start_time: " . $this->debugFormatInt64($this->start_time) . "\n";
+      }
+      if (isset($this->end_time)) {
+        $res .= $prefix . "end_time: " . $this->debugFormatInt64($this->end_time) . "\n";
+      }
+      if (isset($this->offset)) {
+        $res .= $prefix . "offset <\n" . $this->offset->shortDebugString($prefix . "  ") . $prefix . ">\n";
+      }
+      foreach ($this->request_id as $value) {
+        $res .= $prefix . "request_id: " . $this->debugFormatString($value) . "\n";
+      }
+      if (isset($this->minimum_log_level)) {
+        $res .= $prefix . "minimum_log_level: " . $this->debugFormatInt32($this->minimum_log_level) . "\n";
+      }
+      if (isset($this->include_incomplete)) {
+        $res .= $prefix . "include_incomplete: " . $this->debugFormatBool($this->include_incomplete) . "\n";
+      }
+      if (isset($this->count)) {
+        $res .= $prefix . "count: " . $this->debugFormatInt64($this->count) . "\n";
+      }
+      if (isset($this->include_app_logs)) {
+        $res .= $prefix . "include_app_logs: " . $this->debugFormatBool($this->include_app_logs) . "\n";
+      }
+      if (isset($this->include_host)) {
+        $res .= $prefix . "include_host: " . $this->debugFormatBool($this->include_host) . "\n";
+      }
+      if (isset($this->include_all)) {
+        $res .= $prefix . "include_all: " . $this->debugFormatBool($this->include_all) . "\n";
+      }
+      if (isset($this->cache_iterator)) {
+        $res .= $prefix . "cache_iterator: " . $this->debugFormatBool($this->cache_iterator) . "\n";
+      }
+      if (isset($this->combined_log_regex)) {
+        $res .= $prefix . "combined_log_regex: " . $this->debugFormatString($this->combined_log_regex) . "\n";
+      }
+      if (isset($this->host_regex)) {
+        $res .= $prefix . "host_regex: " . $this->debugFormatString($this->host_regex) . "\n";
+      }
+      if (isset($this->replica_index)) {
+        $res .= $prefix . "replica_index: " . $this->debugFormatInt32($this->replica_index) . "\n";
+      }
+      if (isset($this->app_logs_per_request)) {
+        $res .= $prefix . "app_logs_per_request: " . $this->debugFormatInt32($this->app_logs_per_request) . "\n";
+      }
+      if (isset($this->num_shards)) {
+        $res .= $prefix . "num_shards: " . $this->debugFormatInt32($this->num_shards) . "\n";
+      }
+      foreach ($this->module_version as $value) {
+        $res .= $prefix . "module_version <\n" . $value->shortDebugString($prefix . "  ") . $prefix . ">\n";
+      }
+      return $res;
+    }
+  }
+}
+namespace google\appengine {
+  class LogReadResponse extends \google\net\ProtocolMessage {
+    private $log = array();
+    public function getLogSize() {
+      return sizeof($this->log);
+    }
+    public function getLogList() {
+      return $this->log;
+    }
+    public function mutableLog($idx) {
+      if (!isset($this->log[$idx])) {
+        $val = new \google\appengine\RequestLog();
+        $this->log[$idx] = $val;
+        return $val;
+      }
+      return $this->log[$idx];
+    }
+    public function getLog($idx) {
+      if (isset($this->log[$idx])) {
+        return $this->log[$idx];
+      }
+      if ($idx >= end(array_keys($this->log))) {
+        throw new \OutOfRangeException('index out of range: ' + $idx);
+      }
+      return new \google\appengine\RequestLog();
+    }
+    public function addLog() {
+      $val = new \google\appengine\RequestLog();
+      $this->log[] = $val;
+      return $val;
+    }
+    public function clearLog() {
+      $this->log = array();
+    }
+    public function getOffset() {
+      if (!isset($this->offset)) {
+        return new \google\appengine\LogOffset();
+      }
+      return $this->offset;
+    }
+    public function mutableOffset() {
+      if (!isset($this->offset)) {
+        $res = new \google\appengine\LogOffset();
+        $this->offset = $res;
+        return $res;
+      }
+      return $this->offset;
+    }
+    public function clearOffset() {
+      if (isset($this->offset)) {
+        unset($this->offset);
+      }
+    }
+    public function hasOffset() {
+      return isset($this->offset);
+    }
+    public function getLastEndTime() {
+      if (!isset($this->last_end_time)) {
+        return "0";
+      }
+      return $this->last_end_time;
+    }
+    public function setLastEndTime($val) {
+      if (is_double($val)) {
+        $this->last_end_time = sprintf('%0.0F', $val);
+      } else {
+        $this->last_end_time = $val;
+      }
+      return $this;
+    }
+    public function clearLastEndTime() {
+      unset($this->last_end_time);
+      return $this;
+    }
+    public function hasLastEndTime() {
+      return isset($this->last_end_time);
+    }
+    public function clear() {
+      $this->clearLog();
+      $this->clearOffset();
+      $this->clearLastEndTime();
+    }
+    public function byteSizePartial() {
+      $res = 0;
+      $this->checkProtoArray($this->log);
+      $res += 1 * sizeof($this->log);
+      foreach ($this->log as $value) {
+        $res += $this->lengthString($value->byteSizePartial());
+      }
+      if (isset($this->offset)) {
+        $res += 1;
+        $res += $this->lengthString($this->offset->byteSizePartial());
+      }
+      if (isset($this->last_end_time)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->last_end_time);
+      }
+      return $res;
+    }
+    public function outputPartial($out) {
+      $this->checkProtoArray($this->log);
+      foreach ($this->log as $value) {
+        $out->putVarInt32(10);
+        $out->putVarInt32($value->byteSizePartial());
+        $value->outputPartial($out);
+      }
+      if (isset($this->offset)) {
+        $out->putVarInt32(18);
+        $out->putVarInt32($this->offset->byteSizePartial());
+        $this->offset->outputPartial($out);
+      }
+      if (isset($this->last_end_time)) {
+        $out->putVarInt32(24);
+        $out->putVarInt64($this->last_end_time);
+      }
+    }
+    public function tryMerge($d) {
+      while($d->avail() > 0) {
+        $tt = $d->getVarInt32();
+        switch ($tt) {
+          case 10:
+            $length = $d->getVarInt32();
+            $tmp = new \google\net\Decoder($d->buffer(), $d->pos(), $d->pos() + $length);
+            $d->skip($length);
+            $this->addLog()->tryMerge($tmp);
+            break;
+          case 18:
+            $length = $d->getVarInt32();
+            $tmp = new \google\net\Decoder($d->buffer(), $d->pos(), $d->pos() + $length);
+            $d->skip($length);
+            $this->mutableOffset()->tryMerge($tmp);
+            break;
+          case 24:
+            $this->setLastEndTime($d->getVarInt64());
+            break;
+          case 0:
+            throw new \google\net\ProtocolBufferDecodeError();
+            break;
+          default:
+            $d->skipData($tt);
+        }
+      };
+    }
+    public function checkInitialized() {
+      foreach ($this->log as $value) {
+        if (!$value->isInitialized()) return 'log';
+      }
+      if (isset($this->offset) && (!$this->offset->isInitialized())) return 'offset';
+      return null;
+    }
+    public function mergeFrom($x) {
+      if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); }
+      foreach ($x->getLogList() as $v) {
+        $this->addLog()->copyFrom($v);
+      }
+      if ($x->hasOffset()) {
+        $this->mutableOffset()->mergeFrom($x->getOffset());
+      }
+      if ($x->hasLastEndTime()) {
+        $this->setLastEndTime($x->getLastEndTime());
+      }
+    }
+    public function equals($x) {
+      if ($x === $this) { return true; }
+      if (sizeof($this->log) !== sizeof($x->log)) return false;
+      foreach (array_map(null, $this->log, $x->log) as $v) {
+        if (!$v[0]->equals($v[1])) return false;
+      }
+      if (isset($this->offset) !== isset($x->offset)) return false;
+      if (isset($this->offset) && !$this->offset->equals($x->offset)) return false;
+      if (isset($this->last_end_time) !== isset($x->last_end_time)) return false;
+      if (isset($this->last_end_time) && !$this->integerEquals($this->last_end_time, $x->last_end_time)) return false;
+      return true;
+    }
+    public function shortDebugString($prefix = "") {
+      $res = '';
+      foreach ($this->log as $value) {
+        $res .= $prefix . "log <\n" . $value->shortDebugString($prefix . "  ") . $prefix . ">\n";
+      }
+      if (isset($this->offset)) {
+        $res .= $prefix . "offset <\n" . $this->offset->shortDebugString($prefix . "  ") . $prefix . ">\n";
+      }
+      if (isset($this->last_end_time)) {
+        $res .= $prefix . "last_end_time: " . $this->debugFormatInt64($this->last_end_time) . "\n";
+      }
+      return $res;
+    }
+  }
+}
+namespace google\appengine {
+  class LogUsageRecord extends \google\net\ProtocolMessage {
+    public function getVersionId() {
+      if (!isset($this->version_id)) {
+        return '';
+      }
+      return $this->version_id;
+    }
+    public function setVersionId($val) {
+      $this->version_id = $val;
+      return $this;
+    }
+    public function clearVersionId() {
+      unset($this->version_id);
+      return $this;
+    }
+    public function hasVersionId() {
+      return isset($this->version_id);
+    }
+    public function getStartTime() {
+      if (!isset($this->start_time)) {
+        return 0;
+      }
+      return $this->start_time;
+    }
+    public function setStartTime($val) {
+      $this->start_time = $val;
+      return $this;
+    }
+    public function clearStartTime() {
+      unset($this->start_time);
+      return $this;
+    }
+    public function hasStartTime() {
+      return isset($this->start_time);
+    }
+    public function getEndTime() {
+      if (!isset($this->end_time)) {
+        return 0;
+      }
+      return $this->end_time;
+    }
+    public function setEndTime($val) {
+      $this->end_time = $val;
+      return $this;
+    }
+    public function clearEndTime() {
+      unset($this->end_time);
+      return $this;
+    }
+    public function hasEndTime() {
+      return isset($this->end_time);
+    }
+    public function getCount() {
+      if (!isset($this->count)) {
+        return "0";
+      }
+      return $this->count;
+    }
+    public function setCount($val) {
+      if (is_double($val)) {
+        $this->count = sprintf('%0.0F', $val);
+      } else {
+        $this->count = $val;
+      }
+      return $this;
+    }
+    public function clearCount() {
+      unset($this->count);
+      return $this;
+    }
+    public function hasCount() {
+      return isset($this->count);
+    }
+    public function getTotalSize() {
+      if (!isset($this->total_size)) {
+        return "0";
+      }
+      return $this->total_size;
+    }
+    public function setTotalSize($val) {
+      if (is_double($val)) {
+        $this->total_size = sprintf('%0.0F', $val);
+      } else {
+        $this->total_size = $val;
+      }
+      return $this;
+    }
+    public function clearTotalSize() {
+      unset($this->total_size);
+      return $this;
+    }
+    public function hasTotalSize() {
+      return isset($this->total_size);
+    }
+    public function getRecords() {
+      if (!isset($this->records)) {
+        return 0;
+      }
+      return $this->records;
+    }
+    public function setRecords($val) {
+      $this->records = $val;
+      return $this;
+    }
+    public function clearRecords() {
+      unset($this->records);
+      return $this;
+    }
+    public function hasRecords() {
+      return isset($this->records);
+    }
+    public function clear() {
+      $this->clearVersionId();
+      $this->clearStartTime();
+      $this->clearEndTime();
+      $this->clearCount();
+      $this->clearTotalSize();
+      $this->clearRecords();
+    }
+    public function byteSizePartial() {
+      $res = 0;
+      if (isset($this->version_id)) {
+        $res += 1;
+        $res += $this->lengthString(strlen($this->version_id));
+      }
+      if (isset($this->start_time)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->start_time);
+      }
+      if (isset($this->end_time)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->end_time);
+      }
+      if (isset($this->count)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->count);
+      }
+      if (isset($this->total_size)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->total_size);
+      }
+      if (isset($this->records)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->records);
+      }
+      return $res;
+    }
+    public function outputPartial($out) {
+      if (isset($this->version_id)) {
+        $out->putVarInt32(10);
+        $out->putPrefixedString($this->version_id);
+      }
+      if (isset($this->start_time)) {
+        $out->putVarInt32(16);
+        $out->putVarInt32($this->start_time);
+      }
+      if (isset($this->end_time)) {
+        $out->putVarInt32(24);
+        $out->putVarInt32($this->end_time);
+      }
+      if (isset($this->count)) {
+        $out->putVarInt32(32);
+        $out->putVarInt64($this->count);
+      }
+      if (isset($this->total_size)) {
+        $out->putVarInt32(40);
+        $out->putVarInt64($this->total_size);
+      }
+      if (isset($this->records)) {
+        $out->putVarInt32(48);
+        $out->putVarInt32($this->records);
+      }
+    }
+    public function tryMerge($d) {
+      while($d->avail() > 0) {
+        $tt = $d->getVarInt32();
+        switch ($tt) {
+          case 10:
+            $length = $d->getVarInt32();
+            $this->setVersionId(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 16:
+            $this->setStartTime($d->getVarInt32());
+            break;
+          case 24:
+            $this->setEndTime($d->getVarInt32());
+            break;
+          case 32:
+            $this->setCount($d->getVarInt64());
+            break;
+          case 40:
+            $this->setTotalSize($d->getVarInt64());
+            break;
+          case 48:
+            $this->setRecords($d->getVarInt32());
+            break;
+          case 0:
+            throw new \google\net\ProtocolBufferDecodeError();
+            break;
+          default:
+            $d->skipData($tt);
+        }
+      };
+    }
+    public function checkInitialized() {
+      return null;
+    }
+    public function mergeFrom($x) {
+      if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); }
+      if ($x->hasVersionId()) {
+        $this->setVersionId($x->getVersionId());
+      }
+      if ($x->hasStartTime()) {
+        $this->setStartTime($x->getStartTime());
+      }
+      if ($x->hasEndTime()) {
+        $this->setEndTime($x->getEndTime());
+      }
+      if ($x->hasCount()) {
+        $this->setCount($x->getCount());
+      }
+      if ($x->hasTotalSize()) {
+        $this->setTotalSize($x->getTotalSize());
+      }
+      if ($x->hasRecords()) {
+        $this->setRecords($x->getRecords());
+      }
+    }
+    public function equals($x) {
+      if ($x === $this) { return true; }
+      if (isset($this->version_id) !== isset($x->version_id)) return false;
+      if (isset($this->version_id) && $this->version_id !== $x->version_id) return false;
+      if (isset($this->start_time) !== isset($x->start_time)) return false;
+      if (isset($this->start_time) && !$this->integerEquals($this->start_time, $x->start_time)) return false;
+      if (isset($this->end_time) !== isset($x->end_time)) return false;
+      if (isset($this->end_time) && !$this->integerEquals($this->end_time, $x->end_time)) return false;
+      if (isset($this->count) !== isset($x->count)) return false;
+      if (isset($this->count) && !$this->integerEquals($this->count, $x->count)) return false;
+      if (isset($this->total_size) !== isset($x->total_size)) return false;
+      if (isset($this->total_size) && !$this->integerEquals($this->total_size, $x->total_size)) return false;
+      if (isset($this->records) !== isset($x->records)) return false;
+      if (isset($this->records) && !$this->integerEquals($this->records, $x->records)) return false;
+      return true;
+    }
+    public function shortDebugString($prefix = "") {
+      $res = '';
+      if (isset($this->version_id)) {
+        $res .= $prefix . "version_id: " . $this->debugFormatString($this->version_id) . "\n";
+      }
+      if (isset($this->start_time)) {
+        $res .= $prefix . "start_time: " . $this->debugFormatInt32($this->start_time) . "\n";
+      }
+      if (isset($this->end_time)) {
+        $res .= $prefix . "end_time: " . $this->debugFormatInt32($this->end_time) . "\n";
+      }
+      if (isset($this->count)) {
+        $res .= $prefix . "count: " . $this->debugFormatInt64($this->count) . "\n";
+      }
+      if (isset($this->total_size)) {
+        $res .= $prefix . "total_size: " . $this->debugFormatInt64($this->total_size) . "\n";
+      }
+      if (isset($this->records)) {
+        $res .= $prefix . "records: " . $this->debugFormatInt32($this->records) . "\n";
+      }
+      return $res;
+    }
+  }
+}
+namespace google\appengine {
+  class LogUsageRequest extends \google\net\ProtocolMessage {
+    private $version_id = array();
+    public function getAppId() {
+      if (!isset($this->app_id)) {
+        return '';
+      }
+      return $this->app_id;
+    }
+    public function setAppId($val) {
+      $this->app_id = $val;
+      return $this;
+    }
+    public function clearAppId() {
+      unset($this->app_id);
+      return $this;
+    }
+    public function hasAppId() {
+      return isset($this->app_id);
+    }
+    public function getVersionIdSize() {
+      return sizeof($this->version_id);
+    }
+    public function getVersionIdList() {
+      return $this->version_id;
+    }
+    public function getVersionId($idx) {
+      return $this->version_id[$idx];
+    }
+    public function setVersionId($idx, $val) {
+      $this->version_id[$idx] = $val;
+      return $this;
+    }
+    public function addVersionId($val) {
+      $this->version_id[] = $val;
+      return $this;
+    }
+    public function clearVersionId() {
+      $this->version_id = array();
+    }
+    public function getStartTime() {
+      if (!isset($this->start_time)) {
+        return 0;
+      }
+      return $this->start_time;
+    }
+    public function setStartTime($val) {
+      $this->start_time = $val;
+      return $this;
+    }
+    public function clearStartTime() {
+      unset($this->start_time);
+      return $this;
+    }
+    public function hasStartTime() {
+      return isset($this->start_time);
+    }
+    public function getEndTime() {
+      if (!isset($this->end_time)) {
+        return 0;
+      }
+      return $this->end_time;
+    }
+    public function setEndTime($val) {
+      $this->end_time = $val;
+      return $this;
+    }
+    public function clearEndTime() {
+      unset($this->end_time);
+      return $this;
+    }
+    public function hasEndTime() {
+      return isset($this->end_time);
+    }
+    public function getResolutionHours() {
+      if (!isset($this->resolution_hours)) {
+        return '1';
+      }
+      return $this->resolution_hours;
+    }
+    public function setResolutionHours($val) {
+      $this->resolution_hours = $val;
+      return $this;
+    }
+    public function clearResolutionHours() {
+      unset($this->resolution_hours);
+      return $this;
+    }
+    public function hasResolutionHours() {
+      return isset($this->resolution_hours);
+    }
+    public function getCombineVersions() {
+      if (!isset($this->combine_versions)) {
+        return false;
+      }
+      return $this->combine_versions;
+    }
+    public function setCombineVersions($val) {
+      $this->combine_versions = $val;
+      return $this;
+    }
+    public function clearCombineVersions() {
+      unset($this->combine_versions);
+      return $this;
+    }
+    public function hasCombineVersions() {
+      return isset($this->combine_versions);
+    }
+    public function getUsageVersion() {
+      if (!isset($this->usage_version)) {
+        return 0;
+      }
+      return $this->usage_version;
+    }
+    public function setUsageVersion($val) {
+      $this->usage_version = $val;
+      return $this;
+    }
+    public function clearUsageVersion() {
+      unset($this->usage_version);
+      return $this;
+    }
+    public function hasUsageVersion() {
+      return isset($this->usage_version);
+    }
+    public function getVersionsOnly() {
+      if (!isset($this->versions_only)) {
+        return false;
+      }
+      return $this->versions_only;
+    }
+    public function setVersionsOnly($val) {
+      $this->versions_only = $val;
+      return $this;
+    }
+    public function clearVersionsOnly() {
+      unset($this->versions_only);
+      return $this;
+    }
+    public function hasVersionsOnly() {
+      return isset($this->versions_only);
+    }
+    public function clear() {
+      $this->clearAppId();
+      $this->clearVersionId();
+      $this->clearStartTime();
+      $this->clearEndTime();
+      $this->clearResolutionHours();
+      $this->clearCombineVersions();
+      $this->clearUsageVersion();
+      $this->clearVersionsOnly();
+    }
+    public function byteSizePartial() {
+      $res = 0;
+      if (isset($this->app_id)) {
+        $res += 1;
+        $res += $this->lengthString(strlen($this->app_id));
+      }
+      $this->checkProtoArray($this->version_id);
+      $res += 1 * sizeof($this->version_id);
+      foreach ($this->version_id as $value) {
+        $res += $this->lengthString(strlen($value));
+      }
+      if (isset($this->start_time)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->start_time);
+      }
+      if (isset($this->end_time)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->end_time);
+      }
+      if (isset($this->resolution_hours)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->resolution_hours);
+      }
+      if (isset($this->combine_versions)) {
+        $res += 2;
+      }
+      if (isset($this->usage_version)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->usage_version);
+      }
+      if (isset($this->versions_only)) {
+        $res += 2;
+      }
+      return $res;
+    }
+    public function outputPartial($out) {
+      if (isset($this->app_id)) {
+        $out->putVarInt32(10);
+        $out->putPrefixedString($this->app_id);
+      }
+      $this->checkProtoArray($this->version_id);
+      foreach ($this->version_id as $value) {
+        $out->putVarInt32(18);
+        $out->putPrefixedString($value);
+      }
+      if (isset($this->start_time)) {
+        $out->putVarInt32(24);
+        $out->putVarInt32($this->start_time);
+      }
+      if (isset($this->end_time)) {
+        $out->putVarInt32(32);
+        $out->putVarInt32($this->end_time);
+      }
+      if (isset($this->resolution_hours)) {
+        $out->putVarInt32(40);
+        $out->putVarUint32($this->resolution_hours);
+      }
+      if (isset($this->combine_versions)) {
+        $out->putVarInt32(48);
+        $out->putBoolean($this->combine_versions);
+      }
+      if (isset($this->usage_version)) {
+        $out->putVarInt32(56);
+        $out->putVarInt32($this->usage_version);
+      }
+      if (isset($this->versions_only)) {
+        $out->putVarInt32(64);
+        $out->putBoolean($this->versions_only);
+      }
+    }
+    public function tryMerge($d) {
+      while($d->avail() > 0) {
+        $tt = $d->getVarInt32();
+        switch ($tt) {
+          case 10:
+            $length = $d->getVarInt32();
+            $this->setAppId(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 18:
+            $length = $d->getVarInt32();
+            $this->addVersionId(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 24:
+            $this->setStartTime($d->getVarInt32());
+            break;
+          case 32:
+            $this->setEndTime($d->getVarInt32());
+            break;
+          case 40:
+            $this->setResolutionHours($d->getVarUint32());
+            break;
+          case 48:
+            $this->setCombineVersions($d->getBoolean());
+            break;
+          case 56:
+            $this->setUsageVersion($d->getVarInt32());
+            break;
+          case 64:
+            $this->setVersionsOnly($d->getBoolean());
+            break;
+          case 0:
+            throw new \google\net\ProtocolBufferDecodeError();
+            break;
+          default:
+            $d->skipData($tt);
+        }
+      };
+    }
+    public function checkInitialized() {
+      if (!isset($this->app_id)) return 'app_id';
+      return null;
+    }
+    public function mergeFrom($x) {
+      if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); }
+      if ($x->hasAppId()) {
+        $this->setAppId($x->getAppId());
+      }
+      foreach ($x->getVersionIdList() as $v) {
+        $this->addVersionId($v);
+      }
+      if ($x->hasStartTime()) {
+        $this->setStartTime($x->getStartTime());
+      }
+      if ($x->hasEndTime()) {
+        $this->setEndTime($x->getEndTime());
+      }
+      if ($x->hasResolutionHours()) {
+        $this->setResolutionHours($x->getResolutionHours());
+      }
+      if ($x->hasCombineVersions()) {
+        $this->setCombineVersions($x->getCombineVersions());
+      }
+      if ($x->hasUsageVersion()) {
+        $this->setUsageVersion($x->getUsageVersion());
+      }
+      if ($x->hasVersionsOnly()) {
+        $this->setVersionsOnly($x->getVersionsOnly());
+      }
+    }
+    public function equals($x) {
+      if ($x === $this) { return true; }
+      if (isset($this->app_id) !== isset($x->app_id)) return false;
+      if (isset($this->app_id) && $this->app_id !== $x->app_id) return false;
+      if (sizeof($this->version_id) !== sizeof($x->version_id)) return false;
+      foreach (array_map(null, $this->version_id, $x->version_id) as $v) {
+        if ($v[0] !== $v[1]) return false;
+      }
+      if (isset($this->start_time) !== isset($x->start_time)) return false;
+      if (isset($this->start_time) && !$this->integerEquals($this->start_time, $x->start_time)) return false;
+      if (isset($this->end_time) !== isset($x->end_time)) return false;
+      if (isset($this->end_time) && !$this->integerEquals($this->end_time, $x->end_time)) return false;
+      if (isset($this->resolution_hours) !== isset($x->resolution_hours)) return false;
+      if (isset($this->resolution_hours) && !$this->integerEquals($this->resolution_hours, $x->resolution_hours)) return false;
+      if (isset($this->combine_versions) !== isset($x->combine_versions)) return false;
+      if (isset($this->combine_versions) && $this->combine_versions !== $x->combine_versions) return false;
+      if (isset($this->usage_version) !== isset($x->usage_version)) return false;
+      if (isset($this->usage_version) && !$this->integerEquals($this->usage_version, $x->usage_version)) return false;
+      if (isset($this->versions_only) !== isset($x->versions_only)) return false;
+      if (isset($this->versions_only) && $this->versions_only !== $x->versions_only) return false;
+      return true;
+    }
+    public function shortDebugString($prefix = "") {
+      $res = '';
+      if (isset($this->app_id)) {
+        $res .= $prefix . "app_id: " . $this->debugFormatString($this->app_id) . "\n";
+      }
+      foreach ($this->version_id as $value) {
+        $res .= $prefix . "version_id: " . $this->debugFormatString($value) . "\n";
+      }
+      if (isset($this->start_time)) {
+        $res .= $prefix . "start_time: " . $this->debugFormatInt32($this->start_time) . "\n";
+      }
+      if (isset($this->end_time)) {
+        $res .= $prefix . "end_time: " . $this->debugFormatInt32($this->end_time) . "\n";
+      }
+      if (isset($this->resolution_hours)) {
+        $res .= $prefix . "resolution_hours: " . $this->debugFormatInt64($this->resolution_hours) . "\n";
+      }
+      if (isset($this->combine_versions)) {
+        $res .= $prefix . "combine_versions: " . $this->debugFormatBool($this->combine_versions) . "\n";
+      }
+      if (isset($this->usage_version)) {
+        $res .= $prefix . "usage_version: " . $this->debugFormatInt32($this->usage_version) . "\n";
+      }
+      if (isset($this->versions_only)) {
+        $res .= $prefix . "versions_only: " . $this->debugFormatBool($this->versions_only) . "\n";
+      }
+      return $res;
+    }
+  }
+}
+namespace google\appengine {
+  class LogUsageResponse extends \google\net\ProtocolMessage {
+    private $usage = array();
+    public function getUsageSize() {
+      return sizeof($this->usage);
+    }
+    public function getUsageList() {
+      return $this->usage;
+    }
+    public function mutableUsage($idx) {
+      if (!isset($this->usage[$idx])) {
+        $val = new \google\appengine\LogUsageRecord();
+        $this->usage[$idx] = $val;
+        return $val;
+      }
+      return $this->usage[$idx];
+    }
+    public function getUsage($idx) {
+      if (isset($this->usage[$idx])) {
+        return $this->usage[$idx];
+      }
+      if ($idx >= end(array_keys($this->usage))) {
+        throw new \OutOfRangeException('index out of range: ' + $idx);
+      }
+      return new \google\appengine\LogUsageRecord();
+    }
+    public function addUsage() {
+      $val = new \google\appengine\LogUsageRecord();
+      $this->usage[] = $val;
+      return $val;
+    }
+    public function clearUsage() {
+      $this->usage = array();
+    }
+    public function getSummary() {
+      if (!isset($this->summary)) {
+        return new \google\appengine\LogUsageRecord();
+      }
+      return $this->summary;
+    }
+    public function mutableSummary() {
+      if (!isset($this->summary)) {
+        $res = new \google\appengine\LogUsageRecord();
+        $this->summary = $res;
+        return $res;
+      }
+      return $this->summary;
+    }
+    public function clearSummary() {
+      if (isset($this->summary)) {
+        unset($this->summary);
+      }
+    }
+    public function hasSummary() {
+      return isset($this->summary);
+    }
+    public function clear() {
+      $this->clearUsage();
+      $this->clearSummary();
+    }
+    public function byteSizePartial() {
+      $res = 0;
+      $this->checkProtoArray($this->usage);
+      $res += 1 * sizeof($this->usage);
+      foreach ($this->usage as $value) {
+        $res += $this->lengthString($value->byteSizePartial());
+      }
+      if (isset($this->summary)) {
+        $res += 1;
+        $res += $this->lengthString($this->summary->byteSizePartial());
+      }
+      return $res;
+    }
+    public function outputPartial($out) {
+      $this->checkProtoArray($this->usage);
+      foreach ($this->usage as $value) {
+        $out->putVarInt32(10);
+        $out->putVarInt32($value->byteSizePartial());
+        $value->outputPartial($out);
+      }
+      if (isset($this->summary)) {
+        $out->putVarInt32(18);
+        $out->putVarInt32($this->summary->byteSizePartial());
+        $this->summary->outputPartial($out);
+      }
+    }
+    public function tryMerge($d) {
+      while($d->avail() > 0) {
+        $tt = $d->getVarInt32();
+        switch ($tt) {
+          case 10:
+            $length = $d->getVarInt32();
+            $tmp = new \google\net\Decoder($d->buffer(), $d->pos(), $d->pos() + $length);
+            $d->skip($length);
+            $this->addUsage()->tryMerge($tmp);
+            break;
+          case 18:
+            $length = $d->getVarInt32();
+            $tmp = new \google\net\Decoder($d->buffer(), $d->pos(), $d->pos() + $length);
+            $d->skip($length);
+            $this->mutableSummary()->tryMerge($tmp);
+            break;
+          case 0:
+            throw new \google\net\ProtocolBufferDecodeError();
+            break;
+          default:
+            $d->skipData($tt);
+        }
+      };
+    }
+    public function checkInitialized() {
+      foreach ($this->usage as $value) {
+        if (!$value->isInitialized()) return 'usage';
+      }
+      if (isset($this->summary) && (!$this->summary->isInitialized())) return 'summary';
+      return null;
+    }
+    public function mergeFrom($x) {
+      if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); }
+      foreach ($x->getUsageList() as $v) {
+        $this->addUsage()->copyFrom($v);
+      }
+      if ($x->hasSummary()) {
+        $this->mutableSummary()->mergeFrom($x->getSummary());
+      }
+    }
+    public function equals($x) {
+      if ($x === $this) { return true; }
+      if (sizeof($this->usage) !== sizeof($x->usage)) return false;
+      foreach (array_map(null, $this->usage, $x->usage) as $v) {
+        if (!$v[0]->equals($v[1])) return false;
+      }
+      if (isset($this->summary) !== isset($x->summary)) return false;
+      if (isset($this->summary) && !$this->summary->equals($x->summary)) return false;
+      return true;
+    }
+    public function shortDebugString($prefix = "") {
+      $res = '';
+      foreach ($this->usage as $value) {
+        $res .= $prefix . "usage <\n" . $value->shortDebugString($prefix . "  ") . $prefix . ">\n";
+      }
+      if (isset($this->summary)) {
+        $res .= $prefix . "summary <\n" . $this->summary->shortDebugString($prefix . "  ") . $prefix . ">\n";
+      }
+      return $res;
+    }
+  }
+}
diff --git a/php/sdk/google/appengine/api/modules/ModulesService.php b/php/sdk/google/appengine/api/modules/ModulesService.php
index 0b8be0c..3c989ad 100644
--- a/php/sdk/google/appengine/api/modules/ModulesService.php
+++ b/php/sdk/google/appengine/api/modules/ModulesService.php
@@ -28,25 +28,25 @@
 require_once 'google/appengine/runtime/ApiProxy.php';
 require_once 'google/appengine/runtime/ApplicationError.php';
 
-use \google\appengine\runtime\ApiProxy;
-use \google\appengine\runtime\ApplicationError;
-use \google\appengine\GetDefaultVersionRequest;
-use \google\appengine\GetDefaultVersionResponse;
-use \google\appengine\GetHostnameRequest;
-use \google\appengine\GetHostnameResponse;
-use \google\appengine\GetModulesRequest;
-use \google\appengine\GetModulesResponse;
-use \google\appengine\GetNumInstancesRequest;
-use \google\appengine\GetNumInstancesResponse;
-use \google\appengine\GetVersionsRequest;
-use \google\appengine\GetVersionsResponse;
-use \google\appengine\ModulesServiceError\ErrorCode;
-use \google\appengine\SetNumInstancesRequest;
-use \google\appengine\SetNumInstancesResponse;
-use \google\appengine\StartModuleRequest;
-use \google\appengine\StartModuleResponse;
-use \google\appengine\StopModuleRequest;
-use \google\appengine\StopModuleResponse;
+use google\appengine\runtime\ApiProxy;
+use google\appengine\runtime\ApplicationError;
+use google\appengine\GetDefaultVersionRequest;
+use google\appengine\GetDefaultVersionResponse;
+use google\appengine\GetHostnameRequest;
+use google\appengine\GetHostnameResponse;
+use google\appengine\GetModulesRequest;
+use google\appengine\GetModulesResponse;
+use google\appengine\GetNumInstancesRequest;
+use google\appengine\GetNumInstancesResponse;
+use google\appengine\GetVersionsRequest;
+use google\appengine\GetVersionsResponse;
+use google\appengine\ModulesServiceError\ErrorCode;
+use google\appengine\SetNumInstancesRequest;
+use google\appengine\SetNumInstancesResponse;
+use google\appengine\StartModuleRequest;
+use google\appengine\StartModuleResponse;
+use google\appengine\StopModuleRequest;
+use google\appengine\StopModuleResponse;
 
 final class ModulesService {
   private static function errorCodeToException($error) {
@@ -93,14 +93,12 @@
    *
    * @return string The name of the current module. For example, if this is
    * instance 2 of version "v1" of module "module5" for app "my-app", this
-   * function will return "2". Will return null for automatically-scaled
-   * modules.
+   * function will return "2". For automatically-scaled modules, this function
+   * will return a unique hex string for the instance (e.g.
+   * "00c61b117c7f7fd0ce9e1325a04b8f0df30deaaf").
    */
   public static function getCurrentInstanceId() {
-    if (array_key_exists('INSTANCE_ID', $_SERVER)) {
-      return $_SERVER['INSTANCE_ID'];
-    }
-    return null;
+    return $_SERVER['INSTANCE_ID'];
   }
 
   /**
diff --git a/php/sdk/google/appengine/api/modules/ModulesServiceTest.php b/php/sdk/google/appengine/api/modules/ModulesServiceTest.php
index 053314c..64e8bd5 100644
--- a/php/sdk/google/appengine/api/modules/ModulesServiceTest.php
+++ b/php/sdk/google/appengine/api/modules/ModulesServiceTest.php
@@ -26,25 +26,25 @@
 require_once 'google/appengine/runtime/ApplicationError.php';
 require_once 'google/appengine/testing/ApiProxyTestBase.php';
 
-use \google\appengine\runtime\ApplicationError;
-use \google\appengine\testing\ApiProxyTestBase;
-use \google\appengine\GetDefaultVersionRequest;
-use \google\appengine\GetDefaultVersionResponse;
-use \google\appengine\GetHostnameRequest;
-use \google\appengine\GetHostnameResponse;
-use \google\appengine\GetModulesRequest;
-use \google\appengine\GetModulesResponse;
-use \google\appengine\GetNumInstancesRequest;
-use \google\appengine\GetNumInstancesResponse;
-use \google\appengine\GetVersionsRequest;
-use \google\appengine\GetVersionsResponse;
-use \google\appengine\ModulesServiceError\ErrorCode;
-use \google\appengine\SetNumInstancesRequest;
-use \google\appengine\SetNumInstancesResponse;
-use \google\appengine\StartModuleRequest;
-use \google\appengine\StartModuleResponse;
-use \google\appengine\StopModuleRequest;
-use \google\appengine\StopModuleResponse;
+use google\appengine\runtime\ApplicationError;
+use google\appengine\testing\ApiProxyTestBase;
+use google\appengine\GetDefaultVersionRequest;
+use google\appengine\GetDefaultVersionResponse;
+use google\appengine\GetHostnameRequest;
+use google\appengine\GetHostnameResponse;
+use google\appengine\GetModulesRequest;
+use google\appengine\GetModulesResponse;
+use google\appengine\GetNumInstancesRequest;
+use google\appengine\GetNumInstancesResponse;
+use google\appengine\GetVersionsRequest;
+use google\appengine\GetVersionsResponse;
+use google\appengine\ModulesServiceError\ErrorCode;
+use google\appengine\SetNumInstancesRequest;
+use google\appengine\SetNumInstancesResponse;
+use google\appengine\StartModuleRequest;
+use google\appengine\StartModuleResponse;
+use google\appengine\StopModuleRequest;
+use google\appengine\StopModuleResponse;
 
 
 class ModulesTest extends ApiProxyTestBase {
@@ -76,10 +76,6 @@
     $this->assertEquals('v1', ModulesService::getCurrentVersionName());
   }
 
-  public function testGetCurrentInstanceIdNoneSet() {
-    $this->assertEquals(null, ModulesService::getCurrentInstanceId());
-  }
-
   public function testGetCurrentInstanceId() {
     $_SERVER['INSTANCE_ID'] = '123';
     $this->assertEquals('123', ModulesService::getCurrentInstanceId());
diff --git a/php/sdk/google/appengine/api/taskqueue/PushQueue.php b/php/sdk/google/appengine/api/taskqueue/PushQueue.php
index 3313ce5..fd1b481 100644
--- a/php/sdk/google/appengine/api/taskqueue/PushQueue.php
+++ b/php/sdk/google/appengine/api/taskqueue/PushQueue.php
@@ -29,14 +29,14 @@
 require_once 'google/appengine/runtime/ApiProxy.php';
 require_once 'google/appengine/runtime/ApplicationError.php';
 
-use \google\appengine\runtime\ApiProxy;
-use \google\appengine\runtime\ApplicationError;
-use \google\appengine\TaskQueueAddRequest;
-use \google\appengine\TaskQueueAddRequest\RequestMethod;
-use \google\appengine\TaskQueueAddResponse;
-use \google\appengine\TaskQueueBulkAddRequest;
-use \google\appengine\TaskQueueBulkAddResponse;
-use \google\appengine\TaskQueueServiceError\ErrorCode;
+use google\appengine\runtime\ApiProxy;
+use google\appengine\runtime\ApplicationError;
+use google\appengine\TaskQueueAddRequest;
+use google\appengine\TaskQueueAddRequest\RequestMethod;
+use google\appengine\TaskQueueAddResponse;
+use google\appengine\TaskQueueBulkAddRequest;
+use google\appengine\TaskQueueBulkAddResponse;
+use google\appengine\TaskQueueServiceError\ErrorCode;
 
 
 /**
@@ -52,10 +52,10 @@
   private $name;
 
   private static $methods = [
-    'POST'   => RequestMethod::POST,
-    'GET'    => RequestMethod::GET,
-    'HEAD'   => RequestMethod::HEAD,
-    'PUT'    => RequestMethod::PUT,
+    'POST' => RequestMethod::POST,
+    'GET' => RequestMethod::GET,
+    'HEAD' => RequestMethod::HEAD,
+    'PUT' => RequestMethod::PUT,
     'DELETE' => RequestMethod::DELETE
   ];
 
diff --git a/php/sdk/google/appengine/api/taskqueue/PushQueueTest.php b/php/sdk/google/appengine/api/taskqueue/PushQueueTest.php
index 184b63f..e6fc02a 100644
--- a/php/sdk/google/appengine/api/taskqueue/PushQueueTest.php
+++ b/php/sdk/google/appengine/api/taskqueue/PushQueueTest.php
@@ -27,10 +27,10 @@
 
 use google\appengine\api\taskqueue\PushTask;
 use google\appengine\testing\ApiProxyTestBase;
-use \google\appengine\TaskQueueAddRequest\RequestMethod;
-use \google\appengine\TaskQueueBulkAddRequest;
-use \google\appengine\TaskQueueBulkAddResponse;
-use \google\appengine\TaskQueueServiceError\ErrorCode;
+use google\appengine\TaskQueueAddRequest\RequestMethod;
+use google\appengine\TaskQueueBulkAddRequest;
+use google\appengine\TaskQueueBulkAddResponse;
+use google\appengine\TaskQueueServiceError\ErrorCode;
 
 $mockTime = 12345.6;
 
diff --git a/php/sdk/google/appengine/api/taskqueue/PushTask.php b/php/sdk/google/appengine/api/taskqueue/PushTask.php
index 4aca0e9..04730bc 100644
--- a/php/sdk/google/appengine/api/taskqueue/PushTask.php
+++ b/php/sdk/google/appengine/api/taskqueue/PushTask.php
@@ -35,7 +35,7 @@
 require_once 'google/appengine/api/taskqueue/PushQueue.php';
 require_once 'google/appengine/api/taskqueue/taskqueue_service_pb.php';
 
-use \google\appengine\TaskQueueAddRequest\RequestMethod;
+use google\appengine\TaskQueueAddRequest\RequestMethod;
 
 
 /**
diff --git a/php/sdk/google/appengine/api/taskqueue/PushTaskTest.php b/php/sdk/google/appengine/api/taskqueue/PushTaskTest.php
index d69e8a1..eb0d262 100644
--- a/php/sdk/google/appengine/api/taskqueue/PushTaskTest.php
+++ b/php/sdk/google/appengine/api/taskqueue/PushTaskTest.php
@@ -26,10 +26,10 @@
 
 use google\appengine\api\taskqueue\PushTask;
 use google\appengine\testing\ApiProxyTestBase;
-use \google\appengine\TaskQueueAddRequest\RequestMethod;
-use \google\appengine\TaskQueueBulkAddRequest;
-use \google\appengine\TaskQueueBulkAddResponse;
-use \google\appengine\TaskQueueServiceError\ErrorCode;
+use google\appengine\TaskQueueAddRequest\RequestMethod;
+use google\appengine\TaskQueueBulkAddRequest;
+use google\appengine\TaskQueueBulkAddResponse;
+use google\appengine\TaskQueueServiceError\ErrorCode;
 
 $mockTime = 12345.6;
 
diff --git a/php/sdk/google/appengine/api/users/User.php b/php/sdk/google/appengine/api/users/User.php
index 4e92c2a..ff55c0a 100644
--- a/php/sdk/google/appengine/api/users/User.php
+++ b/php/sdk/google/appengine/api/users/User.php
@@ -21,7 +21,7 @@
 
 require_once 'google/appengine/util/string_util.php';
 
-use \google\appengine\util as util;
+use google\appengine\util as util;
 
 /**
  * A user.
@@ -61,7 +61,7 @@
       $user_id = null) {
 
     $auth_domain = getenv('AUTH_DOMAIN');
-    assert($auth_domain !== FALSE);
+    assert($auth_domain !== false);
 
     if ($email === null and $federated_identity === null) {
       throw new \InvalidArgumentException(
@@ -90,7 +90,7 @@
         util\endsWith($this->email, $this->auth_domain)) {
       $suffixLen = strlen($this->auth_domain) + 1;
       return substr($this->email, 0, -$suffixLen);
-    } elseif ($this->federated_identity) {
+    } else if ($this->federated_identity) {
       return $this->federated_identity;
     } else {
       return $this->email;
diff --git a/php/sdk/google/appengine/api/users/UserService.php b/php/sdk/google/appengine/api/users/UserService.php
index 0faa091..253a0b7 100644
--- a/php/sdk/google/appengine/api/users/UserService.php
+++ b/php/sdk/google/appengine/api/users/UserService.php
@@ -19,13 +19,13 @@
 
 namespace google\appengine\api\users;
 
-use \google\appengine\CreateLoginURLRequest;
-use \google\appengine\CreateLoginURLResponse;
-use \google\appengine\CreateLogoutURLRequest;
-use \google\appengine\CreateLogoutURLResponse;
-use \google\appengine\runtime\ApiProxy;
-use \google\appengine\runtime\ApplicationError;
-use \google\appengine\UserServiceError\ErrorCode;
+use google\appengine\CreateLoginURLRequest;
+use google\appengine\CreateLoginURLResponse;
+use google\appengine\CreateLogoutURLRequest;
+use google\appengine\CreateLogoutURLResponse;
+use google\appengine\runtime\ApiProxy;
+use google\appengine\runtime\ApplicationError;
+use google\appengine\UserServiceError\ErrorCode;
 
 require_once 'google/appengine/api/user_service_pb.php';
 require_once 'google/appengine/api/users/User.php';
@@ -125,7 +125,7 @@
     // datastore_types.FromPropertyPb creation of a User object, which will set
     // an empty string for the email (since it is a required field of the
     // underlying data representation of this class in the datastore.
-    if ($email === FALSE) {
+    if ($email === false) {
       $email = '';
     }
 
diff --git a/php/sdk/google/appengine/api/users/UserServiceTest.php b/php/sdk/google/appengine/api/users/UserServiceTest.php
index db7b1f8..de390da 100644
--- a/php/sdk/google/appengine/api/users/UserServiceTest.php
+++ b/php/sdk/google/appengine/api/users/UserServiceTest.php
@@ -24,16 +24,12 @@
 use google\appengine\api\users\User;
 use google\appengine\api\users\UserService;
 use google\appengine\testing\ApiProxyTestBase;
-use \google\appengine\UserServiceError;
+use google\appengine\UserServiceError;
 
 /**
  * Unittest for UserService class.
  */
 class UserServiceTest extends ApiProxyTestBase {
-  public function setUp() {
-    parent::setUp();
-  }
-
   public function tearDown() {
     putenv("AUTH_DOMAIN");
     parent::tearDown();
diff --git a/php/sdk/google/appengine/api/users/UserTest.php b/php/sdk/google/appengine/api/users/UserTest.php
index 076dbba..c67d7e9 100644
--- a/php/sdk/google/appengine/api/users/UserTest.php
+++ b/php/sdk/google/appengine/api/users/UserTest.php
@@ -24,7 +24,7 @@
 use google\appengine\api\users\User;
 use google\appengine\api\users\UserService;
 use google\appengine\testing\ApiProxyTestBase;
-use \google\appengine\UserServiceError;
+use google\appengine\UserServiceError;
 
 /**
  * Unittest for User class.
diff --git a/php/sdk/google/appengine/datastore/datastore_v3_pb.php b/php/sdk/google/appengine/datastore/datastore_v3_pb.php
index 4c0b222..97d6fdc 100644
--- a/php/sdk/google/appengine/datastore/datastore_v3_pb.php
+++ b/php/sdk/google/appengine/datastore/datastore_v3_pb.php
@@ -3583,12 +3583,30 @@
     public function hasCommitCost() {
       return isset($this->commitcost);
     }
+    public function getApproximateStorageDelta() {
+      if (!isset($this->approximate_storage_delta)) {
+        return 0;
+      }
+      return $this->approximate_storage_delta;
+    }
+    public function setApproximateStorageDelta($val) {
+      $this->approximate_storage_delta = $val;
+      return $this;
+    }
+    public function clearApproximateStorageDelta() {
+      unset($this->approximate_storage_delta);
+      return $this;
+    }
+    public function hasApproximateStorageDelta() {
+      return isset($this->approximate_storage_delta);
+    }
     public function clear() {
       $this->clearIndexWrites();
       $this->clearIndexWriteBytes();
       $this->clearEntityWrites();
       $this->clearEntityWriteBytes();
       $this->clearCommitCost();
+      $this->clearApproximateStorageDelta();
     }
     public function byteSizePartial() {
       $res = 0;
@@ -3612,6 +3630,10 @@
         $res += 2;
         $res += $this->commitcost->byteSizePartial();
       }
+      if (isset($this->approximate_storage_delta)) {
+        $res += 1;
+        $res += $this->lengthVarInt64($this->approximate_storage_delta);
+      }
       return $res;
     }
     public function outputPartial($out) {
@@ -3636,6 +3658,10 @@
         $this->commitcost->outputPartial($out);
         $out->putVarInt32(44);
       }
+      if (isset($this->approximate_storage_delta)) {
+        $out->putVarInt32(64);
+        $out->putVarInt32($this->approximate_storage_delta);
+      }
     }
     public function tryMerge($d) {
       while($d->avail() > 0) {
@@ -3656,6 +3682,9 @@
           case 43:
             $this->mutableCommitCost()->tryMerge($d);
             break;
+          case 64:
+            $this->setApproximateStorageDelta($d->getVarInt32());
+            break;
           case 0:
             throw new \google\net\ProtocolBufferDecodeError();
             break;
@@ -3685,6 +3714,9 @@
       if ($x->hasCommitCost()) {
         $this->mutableCommitCost()->mergeFrom($x->getCommitCost());
       }
+      if ($x->hasApproximateStorageDelta()) {
+        $this->setApproximateStorageDelta($x->getApproximateStorageDelta());
+      }
     }
     public function equals($x) {
       if ($x === $this) { return true; }
@@ -3698,6 +3730,8 @@
       if (isset($this->entity_write_bytes) && !$this->integerEquals($this->entity_write_bytes, $x->entity_write_bytes)) return false;
       if (isset($this->commitcost) !== isset($x->commitcost)) return false;
       if (isset($this->commitcost) && !$this->commitcost->equals($x->commitcost)) return false;
+      if (isset($this->approximate_storage_delta) !== isset($x->approximate_storage_delta)) return false;
+      if (isset($this->approximate_storage_delta) && !$this->integerEquals($this->approximate_storage_delta, $x->approximate_storage_delta)) return false;
       return true;
     }
     public function shortDebugString($prefix = "") {
@@ -3717,6 +3751,9 @@
       if (isset($this->commitcost)) {
         $res .= $prefix . "CommitCost {\n" . $this->commitcost->shortDebugString($prefix . "  ") . $prefix . "}\n";
       }
+      if (isset($this->approximate_storage_delta)) {
+        $res .= $prefix . "approximate_storage_delta: " . $this->debugFormatInt32($this->approximate_storage_delta) . "\n";
+      }
       return $res;
     }
   }
diff --git a/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageClient.php b/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageClient.php
index 7fb91db..a49dd3f 100644
--- a/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageClient.php
+++ b/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageClient.php
@@ -29,12 +29,12 @@
 require_once 'google/appengine/runtime/ApplicationError.php';
 require_once 'google/appengine/util/array_util.php';
 
-use \google\appengine\api\app_identity\AppIdentityService;
-use \google\appengine\api\app_identity\AppIdentityException;
-use \google\appengine\runtime\ApiProxy;
-use \google\appengine\runtime\ApplicationError;
-use \google\appengine\URLFetchRequest\RequestMethod;
-use \google\appengine\util as util;
+use google\appengine\api\app_identity\AppIdentityService;
+use google\appengine\api\app_identity\AppIdentityException;
+use google\appengine\runtime\ApiProxy;
+use google\appengine\runtime\ApplicationError;
+use google\appengine\URLFetchRequest\RequestMethod;
+use google\appengine\util as util;
 
 /**
  * CloudStorageClient provides default fail implementations for all of the
@@ -200,7 +200,7 @@
     } else {
       $this->context_options = self::$default_gs_context_options;
     }
-    $this->anonymous = util\FindByKeyOrNull($this->context_options,
+    $this->anonymous = util\findByKeyOrNull($this->context_options,
                                             "anonymous");
 
     $this->url = $this->createObjectUrl($bucket, $object);
diff --git a/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageDirectoryClient.php b/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageDirectoryClient.php
index c5e7be7..52ee334 100644
--- a/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageDirectoryClient.php
+++ b/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageDirectoryClient.php
@@ -25,7 +25,7 @@
 require_once 'google/appengine/ext/cloud_storage_streams/CloudStorageClient.php';
 require_once 'google/appengine/util/string_util.php';
 
-use \google\appengine\util as util;
+use google\appengine\util as util;
 
 /**
  * Client for deleting objects from Google Cloud Storage.
diff --git a/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageStreamWrapper.php b/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageStreamWrapper.php
index f81f02d..d4bc1ee 100644
--- a/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageStreamWrapper.php
+++ b/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageStreamWrapper.php
@@ -32,7 +32,7 @@
 require_once 'google/appengine/ext/cloud_storage_streams/CloudStorageWriteClient.php';
 require_once 'google/appengine/util/array_util.php';
 
-use \google\appengine\util as util;
+use google\appengine\util as util;
 /**
  * Allowed stream_context options.
  * "anonymous": Boolean, if set then OAuth tokens will not be generated.
@@ -338,7 +338,7 @@
     }
     $bucket = $url_parts['host'];
     $object = null;
-    $path = util\FindByKeyOrNull($url_parts, 'path');
+    $path = util\findByKeyOrNull($url_parts, 'path');
     if (isset($path) && $path !== "/") {
       $object = $path;
     }
diff --git a/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageStreamWrapperTest.php b/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageStreamWrapperTest.php
index cb7aecf..1e24225 100644
--- a/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageStreamWrapperTest.php
+++ b/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageStreamWrapperTest.php
@@ -63,12 +63,12 @@
 require_once 'google/appengine/ext/cloud_storage_streams/CloudStorageWriteClient.php';
 require_once 'google/appengine/testing/ApiProxyTestBase.php';
 
-use \google\appengine\testing\ApiProxyTestBase;
-use \google\appengine\ext\cloud_storage_streams\CloudStorageClient;
-use \google\appengine\ext\cloud_storage_streams\CloudStorageReadClient;
-use \google\appengine\ext\cloud_storage_streams\CloudStorageWriteClient;
-use \google\appengine\ext\cloud_storage_streams\HttpResponse;
-use \google\appengine\URLFetchRequest\RequestMethod;
+use google\appengine\testing\ApiProxyTestBase;
+use google\appengine\ext\cloud_storage_streams\CloudStorageClient;
+use google\appengine\ext\cloud_storage_streams\CloudStorageReadClient;
+use google\appengine\ext\cloud_storage_streams\CloudStorageWriteClient;
+use google\appengine\ext\cloud_storage_streams\HttpResponse;
+use google\appengine\URLFetchRequest\RequestMethod;
 
 class CloudStorageStreamWrapperTest extends ApiProxyTestBase {
 
diff --git a/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageUrlStatClient.php b/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageUrlStatClient.php
index 101acdc..f11be36 100644
--- a/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageUrlStatClient.php
+++ b/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageUrlStatClient.php
@@ -24,7 +24,7 @@
 require_once 'google/appengine/ext/cloud_storage_streams/CloudStorageClient.php';
 require_once 'google/appengine/util/string_util.php';
 
-use \google\appengine\util as util;
+use google\appengine\util as util;
 
 /**
  * Client for stating objects in Google Cloud Storage.
diff --git a/php/sdk/google/appengine/runtime/Memcache.php b/php/sdk/google/appengine/runtime/Memcache.php
index 7e6f110..db6e48d 100644
--- a/php/sdk/google/appengine/runtime/Memcache.php
+++ b/php/sdk/google/appengine/runtime/Memcache.php
@@ -24,23 +24,23 @@
  * User provided "flags" arguments are currently ignored and many methods are
  * no-ops.
  */
-use \google\appengine\MemcacheDeleteRequest;
-use \google\appengine\MemcacheDeleteResponse;
-use \google\appengine\MemcacheDeleteResponse\DeleteStatusCode;
-use \google\appengine\MemcacheFlushRequest;
-use \google\appengine\MemcacheFlushResponse;
-use \google\appengine\MemcacheGetRequest;
-use \google\appengine\MemcacheGetResponse;
-use \google\appengine\MemcacheIncrementRequest;
-use \google\appengine\MemcacheIncrementResponse;
-use \google\appengine\MemcacheIncrementResponse\IncrementStatusCode;
-use \google\appengine\MemcacheSetRequest;
-use \google\appengine\MemcacheSetRequest\SetPolicy;
-use \google\appengine\MemcacheSetResponse;
-use \google\appengine\MemcacheSetResponse\SetStatusCode;
-use \google\appengine\runtime\ApiProxy;
-use \google\appengine\runtime\Error;
-use \google\appengine\runtime\MemcacheUtils;
+use google\appengine\MemcacheDeleteRequest;
+use google\appengine\MemcacheDeleteResponse;
+use google\appengine\MemcacheDeleteResponse\DeleteStatusCode;
+use google\appengine\MemcacheFlushRequest;
+use google\appengine\MemcacheFlushResponse;
+use google\appengine\MemcacheGetRequest;
+use google\appengine\MemcacheGetResponse;
+use google\appengine\MemcacheIncrementRequest;
+use google\appengine\MemcacheIncrementResponse;
+use google\appengine\MemcacheIncrementResponse\IncrementStatusCode;
+use google\appengine\MemcacheSetRequest;
+use google\appengine\MemcacheSetRequest\SetPolicy;
+use google\appengine\MemcacheSetResponse;
+use google\appengine\MemcacheSetResponse\SetStatusCode;
+use google\appengine\runtime\ApiProxy;
+use google\appengine\runtime\Error;
+use google\appengine\runtime\MemcacheUtils;
 
 require_once 'google/appengine/api/memcache/memcache_service_pb.php';
 require_once 'google/appengine/runtime/MemcacheUtils.php';
diff --git a/php/sdk/google/appengine/runtime/MemcacheTest.php b/php/sdk/google/appengine/runtime/MemcacheTest.php
index d327895..f1206a2 100644
--- a/php/sdk/google/appengine/runtime/MemcacheTest.php
+++ b/php/sdk/google/appengine/runtime/MemcacheTest.php
@@ -23,20 +23,20 @@
 require_once 'google/appengine/runtime/Memcache.php';
 require_once 'google/appengine/testing/ApiProxyTestBase.php';
 
-use \google\appengine\MemcacheDeleteRequest;
-use \google\appengine\MemcacheDeleteResponse;
-use \google\appengine\MemcacheDeleteResponse\DeleteStatusCode;
-use \google\appengine\MemcacheFlushRequest;
-use \google\appengine\MemcacheFlushResponse;
-use \google\appengine\MemcacheGetRequest;
-use \google\appengine\MemcacheGetResponse;
-use \google\appengine\MemcacheIncrementRequest;
-use \google\appengine\MemcacheIncrementResponse;
-use \google\appengine\MemcacheSetRequest;
-use \google\appengine\MemcacheSetRequest\SetPolicy;
-use \google\appengine\MemcacheSetResponse;
-use \google\appengine\MemcacheSetResponse\SetStatusCode;
-use \google\appengine\testing\ApiProxyTestBase;
+use google\appengine\MemcacheDeleteRequest;
+use google\appengine\MemcacheDeleteResponse;
+use google\appengine\MemcacheDeleteResponse\DeleteStatusCode;
+use google\appengine\MemcacheFlushRequest;
+use google\appengine\MemcacheFlushResponse;
+use google\appengine\MemcacheGetRequest;
+use google\appengine\MemcacheGetResponse;
+use google\appengine\MemcacheIncrementRequest;
+use google\appengine\MemcacheIncrementResponse;
+use google\appengine\MemcacheSetRequest;
+use google\appengine\MemcacheSetRequest\SetPolicy;
+use google\appengine\MemcacheSetResponse;
+use google\appengine\MemcacheSetResponse\SetStatusCode;
+use google\appengine\testing\ApiProxyTestBase;
 
 
 class MemcacheTest extends ApiProxyTestBase {
diff --git a/php/sdk/google/appengine/runtime/MemcacheUtils.php b/php/sdk/google/appengine/runtime/MemcacheUtils.php
index e054c2c..0d488d0 100644
--- a/php/sdk/google/appengine/runtime/MemcacheUtils.php
+++ b/php/sdk/google/appengine/runtime/MemcacheUtils.php
@@ -21,8 +21,8 @@
 
 namespace google\appengine\runtime;
 
-use \google\appengine\MemcacheSetRequest;
-use \google\appengine\MemcacheSetResponse;
+use google\appengine\MemcacheSetRequest;
+use google\appengine\MemcacheSetResponse;
 
 require_once 'google/appengine/api/memcache/memcache_service_pb.php';
 require_once 'google/appengine/runtime/ApiProxy.php';
diff --git a/php/sdk/google/appengine/runtime/Memcached.php b/php/sdk/google/appengine/runtime/Memcached.php
index 38a480d..4774951 100644
--- a/php/sdk/google/appengine/runtime/Memcached.php
+++ b/php/sdk/google/appengine/runtime/Memcached.php
@@ -23,20 +23,20 @@
  *
  */
 
-use \google\appengine\MemcacheDeleteRequest;
-use \google\appengine\MemcacheDeleteResponse;
-use \google\appengine\MemcacheDeleteResponse\DeleteStatusCode;
-use \google\appengine\MemcacheGetRequest;
-use \google\appengine\MemcacheGetResponse;
-use \google\appengine\MemcacheIncrementRequest;
-use \google\appengine\MemcacheIncrementResponse;
-use \google\appengine\MemcacheSetRequest;
-use \google\appengine\MemcacheSetRequest\SetPolicy;
-use \google\appengine\MemcacheSetResponse;
-use \google\appengine\MemcacheSetResponse\SetStatusCode;
-use \google\appengine\runtime\ApiProxy;
-use \google\appengine\runtime\Error;
-use \google\appengine\runtime\MemcacheUtils;
+use google\appengine\MemcacheDeleteRequest;
+use google\appengine\MemcacheDeleteResponse;
+use google\appengine\MemcacheDeleteResponse\DeleteStatusCode;
+use google\appengine\MemcacheGetRequest;
+use google\appengine\MemcacheGetResponse;
+use google\appengine\MemcacheIncrementRequest;
+use google\appengine\MemcacheIncrementResponse;
+use google\appengine\MemcacheSetRequest;
+use google\appengine\MemcacheSetRequest\SetPolicy;
+use google\appengine\MemcacheSetResponse;
+use google\appengine\MemcacheSetResponse\SetStatusCode;
+use google\appengine\runtime\ApiProxy;
+use google\appengine\runtime\Error;
+use google\appengine\runtime\MemcacheUtils;
 
 require_once 'google/appengine/api/memcache/memcache_service_pb.php';
 require_once 'google/appengine/runtime/MemcacheUtils.php';
diff --git a/php/sdk/google/appengine/runtime/MemcachedTest.php b/php/sdk/google/appengine/runtime/MemcachedTest.php
index 123c18e..e75d4ef 100644
--- a/php/sdk/google/appengine/runtime/MemcachedTest.php
+++ b/php/sdk/google/appengine/runtime/MemcachedTest.php
@@ -23,20 +23,20 @@
 require_once 'google/appengine/runtime/Memcached.php';
 require_once 'google/appengine/testing/ApiProxyTestBase.php';
 
-use \google\appengine\MemcacheDeleteRequest;
-use \google\appengine\MemcacheDeleteResponse;
-use \google\appengine\MemcacheDeleteResponse\DeleteStatusCode;
-use \google\appengine\MemcacheFlushRequest;
-use \google\appengine\MemcacheFlushResponse;
-use \google\appengine\MemcacheGetRequest;
-use \google\appengine\MemcacheGetResponse;
-use \google\appengine\MemcacheIncrementRequest;
-use \google\appengine\MemcacheIncrementResponse;
-use \google\appengine\MemcacheSetRequest;
-use \google\appengine\MemcacheSetRequest\SetPolicy;
-use \google\appengine\MemcacheSetResponse;
-use \google\appengine\MemcacheSetResponse\SetStatusCode;
-use \google\appengine\testing\ApiProxyTestBase;
+use google\appengine\MemcacheDeleteRequest;
+use google\appengine\MemcacheDeleteResponse;
+use google\appengine\MemcacheDeleteResponse\DeleteStatusCode;
+use google\appengine\MemcacheFlushRequest;
+use google\appengine\MemcacheFlushResponse;
+use google\appengine\MemcacheGetRequest;
+use google\appengine\MemcacheGetResponse;
+use google\appengine\MemcacheIncrementRequest;
+use google\appengine\MemcacheIncrementResponse;
+use google\appengine\MemcacheSetRequest;
+use google\appengine\MemcacheSetRequest\SetPolicy;
+use google\appengine\MemcacheSetResponse;
+use google\appengine\MemcacheSetResponse\SetStatusCode;
+use google\appengine\testing\ApiProxyTestBase;
 
 class MemcachedTest extends ApiProxyTestBase {
 
diff --git a/php/sdk/google/appengine/runtime/RealApiProxy.php b/php/sdk/google/appengine/runtime/RealApiProxy.php
index d13e351..7668457 100644
--- a/php/sdk/google/appengine/runtime/RealApiProxy.php
+++ b/php/sdk/google/appengine/runtime/RealApiProxy.php
@@ -41,7 +41,7 @@
       $response,
       $deadline = null) {
 
-   if ($deadline === null) {
+    if ($deadline === null) {
       $deadline = self::DEFAULT_DEADLINE_VALUE;
     }
 
diff --git a/php/sdk/google/appengine/runtime/RemoteApiProxy.php b/php/sdk/google/appengine/runtime/RemoteApiProxy.php
index 9354463..f7a7bc7 100644
--- a/php/sdk/google/appengine/runtime/RemoteApiProxy.php
+++ b/php/sdk/google/appengine/runtime/RemoteApiProxy.php
@@ -31,21 +31,24 @@
 require_once 'google/appengine/runtime/FeatureNotEnabledError.php';
 require_once 'google/appengine/runtime/RPCFailedError.php';
 
-use \google\appengine\ext\remote_api\Request;
-use \google\appengine\ext\remote_api\Response;
-use \google\appengine\runtime\RPCFailedError;
+use google\appengine\ext\remote_api\Request;
+use google\appengine\ext\remote_api\Response;
+use google\appengine\runtime\RPCFailedError;
 
 class RemoteApiProxy extends ApiProxyBase{
 
+  private $apiHost = null;
   private $apiPort = null;
   private $requestId = null;
 
   /**
    * Constructs an instance of RemoteApiProxy.
+   * @param string $apiHost Host to use
    * @param int $apiPort Port to use
    * @param string $requestId ID of the request
    */
-  function __construct($apiPort, $requestId) {
+  function __construct($apiHost, $apiPort, $requestId) {
+    $this->apiHost = $apiHost;
     $this->apiPort = $apiPort;
     $this->requestId = $requestId;
   }
@@ -89,7 +92,7 @@
 
     $context = stream_context_create($opts);
     $serialized_remote_respone = file_get_contents(
-        'http://localhost:' . $this->apiPort, false, $context);
+        'http://' . $this->apiHost . ':' . $this->apiPort, false, $context);
     $remote_response = new Response();
     $remote_response->parseFromString($serialized_remote_respone);
 
diff --git a/php/sdk/google/appengine/util/array_util.php b/php/sdk/google/appengine/util/array_util.php
index c155bbb..e0d28a6 100644
--- a/php/sdk/google/appengine/util/array_util.php
+++ b/php/sdk/google/appengine/util/array_util.php
@@ -29,7 +29,7 @@
  * @return mixed The value of the item in the array with the given key, or null
  * if not found.
  */
-function FindByKeyOrNull($array, $key) {
+function findByKeyOrNull($array, $key) {
   if (array_key_exists($key, $array)) {
     return $array[$key];
   }
diff --git a/php/sdk/google/appengine/util/string_util.php b/php/sdk/google/appengine/util/string_util.php
index 73fe24d..9a3fbb0 100644
--- a/php/sdk/google/appengine/util/string_util.php
+++ b/php/sdk/google/appengine/util/string_util.php
@@ -42,4 +42,22 @@
  */
 function startsWith($input, $prefix) {
   return substr($input, 0, strlen($prefix)) === $prefix;
-}
\ No newline at end of file
+}
+
+/**
+ * @param string $input The string which may not be url safe.
+ *
+ * @return string A Base64 encoded url safe string.
+ */
+function base64UrlEncode($input) {
+  return strtr(base64_encode($input), '+/=', '-_,');
+}
+
+/**
+ * @param string $input The url safe Base64 encoded string.
+ *
+ * @return string The original string which may not be url safe.
+ */
+function base64UrlDecode($input) {
+  return base64_decode(strtr($input, '-_,', '+/='));
+}
diff --git a/remote_api_shell.py b/remote_api_shell.py
index 661f26e..fa956c5 100644
--- a/remote_api_shell.py
+++ b/remote_api_shell.py
@@ -114,6 +114,16 @@
 API_SERVER_EXTRA_PATH_SCRIPTS = 'api_server'
 
 
+
+
+ENDPOINTSCFG_EXTRA_PATHS = [
+  os.path.join(DIR_PATH, 'lib', 'cherrypy'),
+  os.path.join(DIR_PATH, 'lib', 'concurrent'),
+  os.path.join(DIR_PATH, 'lib', 'endpoints-1.0'),
+]
+ENDPOINTSCFG_EXTRA_PATH_SCRIPTS = 'endpointscfg'
+
+
 OAUTH_CLIENT_EXTRA_PATHS = [
   os.path.join(DIR_PATH, 'lib', 'google-api-python-client'),
   os.path.join(DIR_PATH, 'lib', 'httplib2'),
@@ -161,6 +171,8 @@
     extra_extra_paths = GOOGLE_SQL_EXTRA_PATHS
   elif re.match(API_SERVER_EXTRA_PATH_SCRIPTS, script_name):
     extra_extra_paths = API_SERVER_EXTRA_PATHS
+  elif re.match(ENDPOINTSCFG_EXTRA_PATH_SCRIPTS, script_name):
+    extra_extra_paths = ENDPOINTSCFG_EXTRA_PATHS
   else:
     extra_extra_paths = []
   fix_sys_path(extra_extra_paths)