App Engine Python SDK version 1.9.1

git-svn-id: http://googleappengine.googlecode.com/svn/trunk/python@416 80f5ef21-4148-0410-bacc-cfb02402ada8
diff --git a/RELEASE_NOTES b/RELEASE_NOTES
index 4fee26b..308daf2 100644
--- a/RELEASE_NOTES
+++ b/RELEASE_NOTES
@@ -3,6 +3,32 @@
 
 App Engine SDK - Release Notes
 
+Version 1.9.1
+
+All
+==============================
+- The Performance Settings section of the Application settings page in the
+  Admin Console, Backends API and all backends related management tools are now
+  deprecated and will be removed in a future release. Users of Backends are
+  recommended to migrate to the App Engine Modules API, which provides a more
+  flexible implementation of the same functionality. These settings are now all
+  configurable via Modules configuration files.
+  See the Modules documentation for more information:
+  https://developers.google.com/appengine/docs/python/modules/
+  #Python_Configuration
+
+PHP
+==============================
+- Fixed an issue with ZendFramework causing App Engine project to crash when
+  using APC caching.
+    https://code.google.com/p/googleappengine/issues/detail?id=9553
+- Fixed an issue with URLFetch not sending strings with null characters
+  correctly.
+    https://code.google.com/p/googleappengine/issues/detail?id=10477
+- Fixed an issue with uploading files to a Google Cloud Storage failing
+  in 1.9.0.
+    https://code.google.com/p/googleappengine/issues/detail?id=10634
+
 Version 1.9.0
 
 All
diff --git a/VERSION b/VERSION
index 4e88d5a..3eddefc 100644
--- a/VERSION
+++ b/VERSION
@@ -1,5 +1,5 @@
-release: "1.9.0"
-timestamp: 1389815587
+release: "1.9.1"
+timestamp: 1393455648
 api_versions: ['1']
 supported_api_versions:
   python:
@@ -8,3 +8,5 @@
     api_versions: ['1']
   go:
     api_versions: ['go1']
+  java7:
+    api_versions: ['1.0']
diff --git a/google/appengine/api/appinfo.py b/google/appengine/api/appinfo.py
index 85bcc2f..e910721 100644
--- a/google/appengine/api/appinfo.py
+++ b/google/appengine/api/appinfo.py
@@ -32,10 +32,11 @@
 
 
 
-import os
 import logging
+import os
 import re
 import string
+import sys
 import wsgiref.util
 
 if os.environ.get('APPENGINE_RUNTIME') == 'python27':
@@ -189,6 +190,8 @@
                       r'(\..*)|'
                       r')$')
 
+SKIP_NO_FILES = r'(?!)'
+
 DEFAULT_NOBUILD_FILES = (r'^$')
 
 
@@ -1330,6 +1333,28 @@
             if result_env_variables else None)
 
 
+def VmSafeSetRuntime(appyaml, runtime):
+  """Sets the runtime while respecting vm runtimes rules for runtime settings.
+
+  Args:
+     appyaml: AppInfoExternal instance, which will be modified.
+     runtime: The runtime to use.
+
+  Returns:
+     The passed in appyaml (which has been modified).
+  """
+  if appyaml.vm:
+    if not appyaml.vm_settings:
+      appyaml.vm_settings = VmSettings()
+
+
+    appyaml.vm_settings['vm_runtime'] = runtime
+    appyaml.runtime = 'vm'
+  else:
+    appyaml.runtime = runtime
+  return appyaml
+
+
 def NormalizeVmSettings(appyaml):
   """Normalize Vm settings.
 
@@ -1349,10 +1374,7 @@
     if not appyaml.vm_settings:
       appyaml.vm_settings = VmSettings()
     if 'vm_runtime' not in appyaml.vm_settings:
-
-
-      appyaml.vm_settings['vm_runtime'] = appyaml.runtime
-      appyaml.runtime = 'vm'
+      appyaml = VmSafeSetRuntime(appyaml, appyaml.runtime)
   return appyaml
 
 
@@ -1361,11 +1383,11 @@
 
   ATTRIBUTES = {
       ENABLE_HEALTH_CHECK: validation.Optional(validation.TYPE_BOOL),
-      CHECK_INTERVAL_SEC: validation.Optional(validation.TYPE_INT),
-      TIMEOUT_SEC: validation.Optional(validation.TYPE_INT),
-      UNHEALTHY_THRESHOLD: validation.Optional(validation.TYPE_INT),
-      HEALTHY_THRESHOLD: validation.Optional(validation.TYPE_INT),
-      RESTART_THRESHOLD: validation.Optional(validation.TYPE_INT),
+      CHECK_INTERVAL_SEC: validation.Optional(validation.Range(0, sys.maxint)),
+      TIMEOUT_SEC: validation.Optional(validation.Range(0, sys.maxint)),
+      UNHEALTHY_THRESHOLD: validation.Optional(validation.Range(0, sys.maxint)),
+      HEALTHY_THRESHOLD: validation.Optional(validation.Range(0, sys.maxint)),
+      RESTART_THRESHOLD: validation.Optional(validation.Range(0, sys.maxint)),
       HOST: validation.Optional(validation.TYPE_STR)}
 
 
@@ -1387,6 +1409,7 @@
       VM: validation.Optional(bool),
       VM_SETTINGS: validation.Optional(VmSettings),
       ENV_VARIABLES: validation.Optional(EnvironmentVariables),
+      SKIP_FILES: validation.RegexStr(default=SKIP_NO_FILES),
 
 
   }
@@ -1449,6 +1472,8 @@
     one.env_variables = EnvironmentVariables.Merge(one.env_variables,
                                                    two.env_variables)
 
+    one.skip_files = cls.MergeSkipFiles(one.skip_files, two.skip_files)
+
     return one
 
   @classmethod
@@ -1516,6 +1541,17 @@
 
     return cls._CommonMergeOps(appinclude_one, appinclude_two)
 
+  @staticmethod
+  def MergeSkipFiles(skip_files_one, skip_files_two):
+    if skip_files_one == SKIP_NO_FILES:
+      return skip_files_two
+    if skip_files_two == SKIP_NO_FILES:
+      return skip_files_one
+    return validation.RegexStr().Validate(
+        [skip_files_one, skip_files_two], SKIP_FILES)
+
+
+
 
 class AppInfoExternal(validation.Validated):
   """Class representing users application info.
@@ -1783,6 +1819,17 @@
     start_handler = URLMap(url=_START_PATH, script=match.start)
     self.handlers.insert(0, start_handler)
 
+  def GetEffectiveRuntime(self):
+    """Returns the app's runtime, resolving VMs to the underlying vm_runtime.
+
+    Returns:
+      The effective runtime: the value of vm_settings.vm_runtime if runtime is
+      "vm", or runtime otherwise.
+    """
+    if self.runtime == 'vm' and hasattr(self, 'vm_settings'):
+      return self.vm_settings.get('vm_runtime')
+    return self.runtime
+
 
 def ValidateHandlers(handlers, is_include_file=False):
   """Validates a list of handler (URLMap) objects.
diff --git a/google/appengine/api/appinfo_includes.py b/google/appengine/api/appinfo_includes.py
index 817f78d..51b830c 100644
--- a/google/appengine/api/appinfo_includes.py
+++ b/google/appengine/api/appinfo_includes.py
@@ -44,13 +44,31 @@
 
 
 def Parse(appinfo_file, open_fn=open):
-  """Parse an AppYaml file and merge referenced includes and builtins."""
+  """Parse an AppYaml file and merge referenced includes and builtins.
+
+  Args:
+    appinfo_file: an opened file, for example the result of open('app.yaml').
+    open_fn: a function to open included files.
+
+  Returns:
+    The parsed appinfo.AppInfoExternal object.
+  """
   appyaml, _ = ParseAndReturnIncludePaths(appinfo_file, open_fn)
   return appyaml
 
 
 def ParseAndReturnIncludePaths(appinfo_file, open_fn=open):
-  """Parse an AppYaml file and merge referenced includes and builtins."""
+  """Parse an AppYaml file and merge referenced includes and builtins.
+
+  Args:
+    appinfo_file: an opened file, for example the result of open('app.yaml').
+    open_fn: a function to open included files.
+
+  Returns:
+    A tuple where the first element is the parsed appinfo.AppInfoExternal
+    object and the second element is a list of the absolute paths of the
+    included files, in no particular order.
+  """
   try:
     appinfo_path = appinfo_file.name
     if not os.path.isfile(appinfo_path):
@@ -93,7 +111,9 @@
              reading yaml files.
 
   Returns:
-    the modified appyaml object which incorporates referenced yaml files.
+    A tuple where the first element is the modified appyaml object
+    incorporating the referenced yaml files, and the second element is a list
+    of the absolute paths of the included files, in no particular order.
   """
 
 
@@ -144,12 +164,14 @@
     open_fn: file opening function udes, used when reading yaml files.
 
   Returns:
-    AppInclude object merged from following all builtins/includes defined in
-    provided AppInclude object.
+    A two-element tuple where the first element is the AppInclude object merged
+    from following all builtins/includes defined in provided AppInclude object;
+    and the second element is a list of the absolute paths of the included
+    files, in no particular order.
 
   Raises:
     IncludeFileNotFound: if file specified in an include statement cannot be
-    resolved to an includeable file (result from _ResolvePath is False).
+      resolved to an includeable file (result from _ResolvePath is False).
   """
 
   class RecurseState(object):
diff --git a/google/appengine/api/backends/backends.py b/google/appengine/api/backends/backends.py
index bc938d4..9926f00 100644
--- a/google/appengine/api/backends/backends.py
+++ b/google/appengine/api/backends/backends.py
@@ -17,7 +17,7 @@
 
 
 
-"""Backends API.
+"""DEPRECATED: Backends API.
 
 This API provides utility methods for working with backends.
 """
@@ -30,6 +30,7 @@
 
 
 
+import logging
 import os
 import re
 
@@ -51,8 +52,16 @@
   pass
 
 
+logging.warning('The Backends API is deprecated and will be removed in a '
+                'future release. Please migrate to the Modules API as soon as '
+                'possible.')
+
+
 def get_backend():
-  """Get the name of the backend handling this request.
+  """DEPRECATED: Get the name of the backend handling this request.
+
+  Warning: This API is deprecated and will be removed in a future
+  release. Please migrate to the Modules API as soon as possible.
 
   Returns:
     string: The current backend, or None if this is not a backend.
@@ -61,7 +70,10 @@
 
 
 def get_instance():
-  """Get the instance number of the backend handling this request.
+  """DEPRECATED: Get the instance number of the backend handling this request.
+
+  Warning: This API is deprecated and will be removed in a future
+  release. Please migrate to the Modules API as soon as possible.
 
   Returns:
     int: The instance, in [0, instances-1], or None if this is not a backend.
@@ -74,7 +86,10 @@
 
 
 def get_url(backend=None, instance=None, protocol='http'):
-  """Returns a URL pointing to a backend or backend instance.
+  """DEPRECATED: Returns a URL pointing to a backend or backend instance.
+
+  Warning: This API is deprecated and will be removed in a future
+  release. Please migrate to the Modules API as soon as possible.
 
   This method works in both production and development environments.
 
@@ -109,7 +124,10 @@
 
 
 def get_hostname(backend=None, instance=None):
-  """Returns the hostname for a backend or backend instance.
+  """DEPRECATED: Returns the hostname for a backend or backend instance.
+
+  Warning: This API is deprecated and will be removed in a future
+  release. Please migrate to the Modules API as soon as possible.
 
   Args:
     backend: The name of the backend. If None, the current backend will be used.
diff --git a/google/appengine/api/files/file.py b/google/appengine/api/files/file.py
index 1c8699b..bb56869 100644
--- a/google/appengine/api/files/file.py
+++ b/google/appengine/api/files/file.py
@@ -756,7 +756,7 @@
     """Return file's current position."""
     return self._position
 
-  def read(self, size):
+  def read(self, size=None):
     """Read data from RAW file.
 
     Args:
@@ -766,6 +766,8 @@
     Returns:
       A string with data read.
     """
+    if size is None:
+      size = sys.maxint
     data_list = []
     while True:
       result = self.__readBuffer(size)
diff --git a/google/appengine/api/labs/taskqueue/taskqueue_stub.py b/google/appengine/api/labs/taskqueue/taskqueue_stub.py
index 820054e..7fafe87 100644
--- a/google/appengine/api/labs/taskqueue/taskqueue_stub.py
+++ b/google/appengine/api/labs/taskqueue/taskqueue_stub.py
@@ -221,7 +221,7 @@
     pos = bisect.bisect_left(self._sorted_by_eta, (eta, name, None))
     if self._sorted_by_eta[pos][2] is not old_task:
       logging.error('task store corrupted')
-      return taskqueue_service_pb.TaskQueueServiceError.INTERNAL_ERRROR
+      return taskqueue_service_pb.TaskQueueServiceError.INTERNAL_ERROR
     self._sorted_by_eta.pop(pos)
     return taskqueue_service_pb.TaskQueueServiceError.OK
 
diff --git a/google/appengine/api/mail.py b/google/appengine/api/mail.py
index 1928ff2..09ae32c 100644
--- a/google/appengine/api/mail.py
+++ b/google/appengine/api/mail.py
@@ -783,7 +783,7 @@
     payload = self.payload
 
 
-    if self.encoding and self.encoding.lower() != '7bit':
+    if self.encoding and self.encoding.lower() not in ('7bit', '8bit'):
       try:
         payload = payload.decode(self.encoding)
       except LookupError:
diff --git a/google/appengine/api/mail_stub.py b/google/appengine/api/mail_stub.py
index 7c42434..6d5da0b 100644
--- a/google/appengine/api/mail_stub.py
+++ b/google/appengine/api/mail_stub.py
@@ -29,6 +29,7 @@
 
 
 
+from email import encoders
 from email import MIMEBase
 from email import MIMEMultipart
 from email import MIMEText
@@ -283,6 +284,7 @@
     import email
 
     mime_message = mail.MailMessageToMIMEMessage(request)
+    _Base64EncodeAttachments(mime_message)
     if self._smtp_host:
 
       self._SendSMTP(mime_message, smtp_lib)
@@ -317,3 +319,15 @@
       log('Both SMTP and sendmail are enabled.  Ignoring sendmail.')
 
   _Dynamic_SendToAdmins = _SendToAdmins
+
+
+def _Base64EncodeAttachments(mime_message):
+  """Base64 encode all individual attachments that are not text.
+
+  Args:
+    mime_message: MimeMessage to process.
+  """
+  for item in mime_message.get_payload():
+    if (item.get_content_maintype() not in ['multipart', 'text'] and
+        'Content-Transfer-Encoding' not in item):
+      encoders.encode_base64(item)
diff --git a/google/appengine/api/search/search.py b/google/appengine/api/search/search.py
index 2800573..d803bff 100644
--- a/google/appengine/api/search/search.py
+++ b/google/appengine/api/search/search.py
@@ -941,7 +941,7 @@
 
 
 class DateField(Field):
-  """A Field that has a date value.
+  """A Field that has a date or datetime value.
 
   The following example shows a date field named creation_date:
     DateField(name='creation_date', value=datetime.date(2011, 03, 11))
@@ -952,10 +952,10 @@
 
     Args:
       name: The name of the field.
-      value: A datetime.date but not a datetime.datetime.
+      value: A datetime.date or a datetime.datetime.
 
     Raises:
-      TypeError: If value is not a datetime.date or is a datetime.datetime.
+      TypeError: If value is not a datetime.date or a datetime.datetime.
     """
     Field.__init__(self, name, value)
 
diff --git a/google/appengine/api/search/search_service_pb.py b/google/appengine/api/search/search_service_pb.py
index be791ae..daa0d2b 100644
--- a/google/appengine/api/search/search_service_pb.py
+++ b/google/appengine/api/search/search_service_pb.py
@@ -4018,6 +4018,1004 @@
   _STYLE = """"""
   _STYLE_CONTENT_TYPE = """"""
   _PROTO_DESCRIPTOR_NAME = 'apphosting.FieldSpec'
+class FacetRange(ProtocolBuffer.ProtocolMessage):
+  has_name_ = 0
+  name_ = ""
+  has_start_ = 0
+  start_ = ""
+  has_end_ = 0
+  end_ = ""
+
+  def __init__(self, contents=None):
+    if contents is not None: self.MergeFromString(contents)
+
+  def name(self): return self.name_
+
+  def set_name(self, x):
+    self.has_name_ = 1
+    self.name_ = x
+
+  def clear_name(self):
+    if self.has_name_:
+      self.has_name_ = 0
+      self.name_ = ""
+
+  def has_name(self): return self.has_name_
+
+  def start(self): return self.start_
+
+  def set_start(self, x):
+    self.has_start_ = 1
+    self.start_ = x
+
+  def clear_start(self):
+    if self.has_start_:
+      self.has_start_ = 0
+      self.start_ = ""
+
+  def has_start(self): return self.has_start_
+
+  def end(self): return self.end_
+
+  def set_end(self, x):
+    self.has_end_ = 1
+    self.end_ = x
+
+  def clear_end(self):
+    if self.has_end_:
+      self.has_end_ = 0
+      self.end_ = ""
+
+  def has_end(self): return self.has_end_
+
+
+  def MergeFrom(self, x):
+    assert x is not self
+    if (x.has_name()): self.set_name(x.name())
+    if (x.has_start()): self.set_start(x.start())
+    if (x.has_end()): self.set_end(x.end())
+
+  def Equals(self, x):
+    if x is self: return 1
+    if self.has_name_ != x.has_name_: return 0
+    if self.has_name_ and self.name_ != x.name_: return 0
+    if self.has_start_ != x.has_start_: return 0
+    if self.has_start_ and self.start_ != x.start_: return 0
+    if self.has_end_ != x.has_end_: return 0
+    if self.has_end_ and self.end_ != x.end_: return 0
+    return 1
+
+  def IsInitialized(self, debug_strs=None):
+    initialized = 1
+    return initialized
+
+  def ByteSize(self):
+    n = 0
+    if (self.has_name_): n += 1 + self.lengthString(len(self.name_))
+    if (self.has_start_): n += 1 + self.lengthString(len(self.start_))
+    if (self.has_end_): n += 1 + self.lengthString(len(self.end_))
+    return n
+
+  def ByteSizePartial(self):
+    n = 0
+    if (self.has_name_): n += 1 + self.lengthString(len(self.name_))
+    if (self.has_start_): n += 1 + self.lengthString(len(self.start_))
+    if (self.has_end_): n += 1 + self.lengthString(len(self.end_))
+    return n
+
+  def Clear(self):
+    self.clear_name()
+    self.clear_start()
+    self.clear_end()
+
+  def OutputUnchecked(self, out):
+    if (self.has_name_):
+      out.putVarInt32(10)
+      out.putPrefixedString(self.name_)
+    if (self.has_start_):
+      out.putVarInt32(18)
+      out.putPrefixedString(self.start_)
+    if (self.has_end_):
+      out.putVarInt32(26)
+      out.putPrefixedString(self.end_)
+
+  def OutputPartial(self, out):
+    if (self.has_name_):
+      out.putVarInt32(10)
+      out.putPrefixedString(self.name_)
+    if (self.has_start_):
+      out.putVarInt32(18)
+      out.putPrefixedString(self.start_)
+    if (self.has_end_):
+      out.putVarInt32(26)
+      out.putPrefixedString(self.end_)
+
+  def TryMerge(self, d):
+    while d.avail() > 0:
+      tt = d.getVarInt32()
+      if tt == 10:
+        self.set_name(d.getPrefixedString())
+        continue
+      if tt == 18:
+        self.set_start(d.getPrefixedString())
+        continue
+      if tt == 26:
+        self.set_end(d.getPrefixedString())
+        continue
+
+
+      if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError
+      d.skipData(tt)
+
+
+  def __str__(self, prefix="", printElemNumber=0):
+    res=""
+    if self.has_name_: res+=prefix+("name: %s\n" % self.DebugFormatString(self.name_))
+    if self.has_start_: res+=prefix+("start: %s\n" % self.DebugFormatString(self.start_))
+    if self.has_end_: res+=prefix+("end: %s\n" % self.DebugFormatString(self.end_))
+    return res
+
+
+  def _BuildTagLookupTable(sparse, maxtag, default=None):
+    return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)])
+
+  kname = 1
+  kstart = 2
+  kend = 3
+
+  _TEXT = _BuildTagLookupTable({
+    0: "ErrorCode",
+    1: "name",
+    2: "start",
+    3: "end",
+  }, 3)
+
+  _TYPES = _BuildTagLookupTable({
+    0: ProtocolBuffer.Encoder.NUMERIC,
+    1: ProtocolBuffer.Encoder.STRING,
+    2: ProtocolBuffer.Encoder.STRING,
+    3: ProtocolBuffer.Encoder.STRING,
+  }, 3, ProtocolBuffer.Encoder.MAX_TYPE)
+
+
+  _STYLE = """"""
+  _STYLE_CONTENT_TYPE = """"""
+  _PROTO_DESCRIPTOR_NAME = 'apphosting.FacetRange'
+class FacetRequestParam(ProtocolBuffer.ProtocolMessage):
+  has_value_limit_ = 0
+  value_limit_ = 0
+
+  def __init__(self, contents=None):
+    self.range_ = []
+    self.value_constraint_ = []
+    if contents is not None: self.MergeFromString(contents)
+
+  def value_limit(self): return self.value_limit_
+
+  def set_value_limit(self, x):
+    self.has_value_limit_ = 1
+    self.value_limit_ = x
+
+  def clear_value_limit(self):
+    if self.has_value_limit_:
+      self.has_value_limit_ = 0
+      self.value_limit_ = 0
+
+  def has_value_limit(self): return self.has_value_limit_
+
+  def range_size(self): return len(self.range_)
+  def range_list(self): return self.range_
+
+  def range(self, i):
+    return self.range_[i]
+
+  def mutable_range(self, i):
+    return self.range_[i]
+
+  def add_range(self):
+    x = FacetRange()
+    self.range_.append(x)
+    return x
+
+  def clear_range(self):
+    self.range_ = []
+  def value_constraint_size(self): return len(self.value_constraint_)
+  def value_constraint_list(self): return self.value_constraint_
+
+  def value_constraint(self, i):
+    return self.value_constraint_[i]
+
+  def set_value_constraint(self, i, x):
+    self.value_constraint_[i] = x
+
+  def add_value_constraint(self, x):
+    self.value_constraint_.append(x)
+
+  def clear_value_constraint(self):
+    self.value_constraint_ = []
+
+
+  def MergeFrom(self, x):
+    assert x is not self
+    if (x.has_value_limit()): self.set_value_limit(x.value_limit())
+    for i in xrange(x.range_size()): self.add_range().CopyFrom(x.range(i))
+    for i in xrange(x.value_constraint_size()): self.add_value_constraint(x.value_constraint(i))
+
+  def Equals(self, x):
+    if x is self: return 1
+    if self.has_value_limit_ != x.has_value_limit_: return 0
+    if self.has_value_limit_ and self.value_limit_ != x.value_limit_: return 0
+    if len(self.range_) != len(x.range_): return 0
+    for e1, e2 in zip(self.range_, x.range_):
+      if e1 != e2: return 0
+    if len(self.value_constraint_) != len(x.value_constraint_): return 0
+    for e1, e2 in zip(self.value_constraint_, x.value_constraint_):
+      if e1 != e2: return 0
+    return 1
+
+  def IsInitialized(self, debug_strs=None):
+    initialized = 1
+    for p in self.range_:
+      if not p.IsInitialized(debug_strs): initialized=0
+    return initialized
+
+  def ByteSize(self):
+    n = 0
+    if (self.has_value_limit_): n += 1 + self.lengthVarInt64(self.value_limit_)
+    n += 1 * len(self.range_)
+    for i in xrange(len(self.range_)): n += self.lengthString(self.range_[i].ByteSize())
+    n += 1 * len(self.value_constraint_)
+    for i in xrange(len(self.value_constraint_)): n += self.lengthString(len(self.value_constraint_[i]))
+    return n
+
+  def ByteSizePartial(self):
+    n = 0
+    if (self.has_value_limit_): n += 1 + self.lengthVarInt64(self.value_limit_)
+    n += 1 * len(self.range_)
+    for i in xrange(len(self.range_)): n += self.lengthString(self.range_[i].ByteSizePartial())
+    n += 1 * len(self.value_constraint_)
+    for i in xrange(len(self.value_constraint_)): n += self.lengthString(len(self.value_constraint_[i]))
+    return n
+
+  def Clear(self):
+    self.clear_value_limit()
+    self.clear_range()
+    self.clear_value_constraint()
+
+  def OutputUnchecked(self, out):
+    if (self.has_value_limit_):
+      out.putVarInt32(8)
+      out.putVarInt32(self.value_limit_)
+    for i in xrange(len(self.range_)):
+      out.putVarInt32(18)
+      out.putVarInt32(self.range_[i].ByteSize())
+      self.range_[i].OutputUnchecked(out)
+    for i in xrange(len(self.value_constraint_)):
+      out.putVarInt32(26)
+      out.putPrefixedString(self.value_constraint_[i])
+
+  def OutputPartial(self, out):
+    if (self.has_value_limit_):
+      out.putVarInt32(8)
+      out.putVarInt32(self.value_limit_)
+    for i in xrange(len(self.range_)):
+      out.putVarInt32(18)
+      out.putVarInt32(self.range_[i].ByteSizePartial())
+      self.range_[i].OutputPartial(out)
+    for i in xrange(len(self.value_constraint_)):
+      out.putVarInt32(26)
+      out.putPrefixedString(self.value_constraint_[i])
+
+  def TryMerge(self, d):
+    while d.avail() > 0:
+      tt = d.getVarInt32()
+      if tt == 8:
+        self.set_value_limit(d.getVarInt32())
+        continue
+      if tt == 18:
+        length = d.getVarInt32()
+        tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length)
+        d.skip(length)
+        self.add_range().TryMerge(tmp)
+        continue
+      if tt == 26:
+        self.add_value_constraint(d.getPrefixedString())
+        continue
+
+
+      if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError
+      d.skipData(tt)
+
+
+  def __str__(self, prefix="", printElemNumber=0):
+    res=""
+    if self.has_value_limit_: res+=prefix+("value_limit: %s\n" % self.DebugFormatInt32(self.value_limit_))
+    cnt=0
+    for e in self.range_:
+      elm=""
+      if printElemNumber: elm="(%d)" % cnt
+      res+=prefix+("range%s <\n" % elm)
+      res+=e.__str__(prefix + "  ", printElemNumber)
+      res+=prefix+">\n"
+      cnt+=1
+    cnt=0
+    for e in self.value_constraint_:
+      elm=""
+      if printElemNumber: elm="(%d)" % cnt
+      res+=prefix+("value_constraint%s: %s\n" % (elm, self.DebugFormatString(e)))
+      cnt+=1
+    return res
+
+
+  def _BuildTagLookupTable(sparse, maxtag, default=None):
+    return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)])
+
+  kvalue_limit = 1
+  krange = 2
+  kvalue_constraint = 3
+
+  _TEXT = _BuildTagLookupTable({
+    0: "ErrorCode",
+    1: "value_limit",
+    2: "range",
+    3: "value_constraint",
+  }, 3)
+
+  _TYPES = _BuildTagLookupTable({
+    0: ProtocolBuffer.Encoder.NUMERIC,
+    1: ProtocolBuffer.Encoder.NUMERIC,
+    2: ProtocolBuffer.Encoder.STRING,
+    3: ProtocolBuffer.Encoder.STRING,
+  }, 3, ProtocolBuffer.Encoder.MAX_TYPE)
+
+
+  _STYLE = """"""
+  _STYLE_CONTENT_TYPE = """"""
+  _PROTO_DESCRIPTOR_NAME = 'apphosting.FacetRequestParam'
+class FacetAutoDetectParam(ProtocolBuffer.ProtocolMessage):
+  has_value_limit_ = 0
+  value_limit_ = 10
+
+  def __init__(self, contents=None):
+    if contents is not None: self.MergeFromString(contents)
+
+  def value_limit(self): return self.value_limit_
+
+  def set_value_limit(self, x):
+    self.has_value_limit_ = 1
+    self.value_limit_ = x
+
+  def clear_value_limit(self):
+    if self.has_value_limit_:
+      self.has_value_limit_ = 0
+      self.value_limit_ = 10
+
+  def has_value_limit(self): return self.has_value_limit_
+
+
+  def MergeFrom(self, x):
+    assert x is not self
+    if (x.has_value_limit()): self.set_value_limit(x.value_limit())
+
+  def Equals(self, x):
+    if x is self: return 1
+    if self.has_value_limit_ != x.has_value_limit_: return 0
+    if self.has_value_limit_ and self.value_limit_ != x.value_limit_: return 0
+    return 1
+
+  def IsInitialized(self, debug_strs=None):
+    initialized = 1
+    return initialized
+
+  def ByteSize(self):
+    n = 0
+    if (self.has_value_limit_): n += 1 + self.lengthVarInt64(self.value_limit_)
+    return n
+
+  def ByteSizePartial(self):
+    n = 0
+    if (self.has_value_limit_): n += 1 + self.lengthVarInt64(self.value_limit_)
+    return n
+
+  def Clear(self):
+    self.clear_value_limit()
+
+  def OutputUnchecked(self, out):
+    if (self.has_value_limit_):
+      out.putVarInt32(8)
+      out.putVarInt32(self.value_limit_)
+
+  def OutputPartial(self, out):
+    if (self.has_value_limit_):
+      out.putVarInt32(8)
+      out.putVarInt32(self.value_limit_)
+
+  def TryMerge(self, d):
+    while d.avail() > 0:
+      tt = d.getVarInt32()
+      if tt == 8:
+        self.set_value_limit(d.getVarInt32())
+        continue
+
+
+      if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError
+      d.skipData(tt)
+
+
+  def __str__(self, prefix="", printElemNumber=0):
+    res=""
+    if self.has_value_limit_: res+=prefix+("value_limit: %s\n" % self.DebugFormatInt32(self.value_limit_))
+    return res
+
+
+  def _BuildTagLookupTable(sparse, maxtag, default=None):
+    return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)])
+
+  kvalue_limit = 1
+
+  _TEXT = _BuildTagLookupTable({
+    0: "ErrorCode",
+    1: "value_limit",
+  }, 1)
+
+  _TYPES = _BuildTagLookupTable({
+    0: ProtocolBuffer.Encoder.NUMERIC,
+    1: ProtocolBuffer.Encoder.NUMERIC,
+  }, 1, ProtocolBuffer.Encoder.MAX_TYPE)
+
+
+  _STYLE = """"""
+  _STYLE_CONTENT_TYPE = """"""
+  _PROTO_DESCRIPTOR_NAME = 'apphosting.FacetAutoDetectParam'
+class FacetRequest(ProtocolBuffer.ProtocolMessage):
+  has_name_ = 0
+  name_ = ""
+  has_type_ = 0
+  has_params_ = 0
+  params_ = None
+
+  def __init__(self, contents=None):
+    self.type_ = ContentType()
+    self.lazy_init_lock_ = thread.allocate_lock()
+    if contents is not None: self.MergeFromString(contents)
+
+  def name(self): return self.name_
+
+  def set_name(self, x):
+    self.has_name_ = 1
+    self.name_ = x
+
+  def clear_name(self):
+    if self.has_name_:
+      self.has_name_ = 0
+      self.name_ = ""
+
+  def has_name(self): return self.has_name_
+
+  def type(self): return self.type_
+
+  def mutable_type(self): self.has_type_ = 1; return self.type_
+
+  def clear_type(self):self.has_type_ = 0; self.type_.Clear()
+
+  def has_type(self): return self.has_type_
+
+  def params(self):
+    if self.params_ is None:
+      self.lazy_init_lock_.acquire()
+      try:
+        if self.params_ is None: self.params_ = FacetRequestParam()
+      finally:
+        self.lazy_init_lock_.release()
+    return self.params_
+
+  def mutable_params(self): self.has_params_ = 1; return self.params()
+
+  def clear_params(self):
+
+    if self.has_params_:
+      self.has_params_ = 0;
+      if self.params_ is not None: self.params_.Clear()
+
+  def has_params(self): return self.has_params_
+
+
+  def MergeFrom(self, x):
+    assert x is not self
+    if (x.has_name()): self.set_name(x.name())
+    if (x.has_type()): self.mutable_type().MergeFrom(x.type())
+    if (x.has_params()): self.mutable_params().MergeFrom(x.params())
+
+  def Equals(self, x):
+    if x is self: return 1
+    if self.has_name_ != x.has_name_: return 0
+    if self.has_name_ and self.name_ != x.name_: return 0
+    if self.has_type_ != x.has_type_: return 0
+    if self.has_type_ and self.type_ != x.type_: return 0
+    if self.has_params_ != x.has_params_: return 0
+    if self.has_params_ and self.params_ != x.params_: return 0
+    return 1
+
+  def IsInitialized(self, debug_strs=None):
+    initialized = 1
+    if (not self.has_name_):
+      initialized = 0
+      if debug_strs is not None:
+        debug_strs.append('Required field: name not set.')
+    if (not self.has_type_):
+      initialized = 0
+      if debug_strs is not None:
+        debug_strs.append('Required field: type not set.')
+    elif not self.type_.IsInitialized(debug_strs): initialized = 0
+    if (self.has_params_ and not self.params_.IsInitialized(debug_strs)): initialized = 0
+    return initialized
+
+  def ByteSize(self):
+    n = 0
+    n += self.lengthString(len(self.name_))
+    n += self.lengthString(self.type_.ByteSize())
+    if (self.has_params_): n += 1 + self.lengthString(self.params_.ByteSize())
+    return n + 2
+
+  def ByteSizePartial(self):
+    n = 0
+    if (self.has_name_):
+      n += 1
+      n += self.lengthString(len(self.name_))
+    if (self.has_type_):
+      n += 1
+      n += self.lengthString(self.type_.ByteSizePartial())
+    if (self.has_params_): n += 1 + self.lengthString(self.params_.ByteSizePartial())
+    return n
+
+  def Clear(self):
+    self.clear_name()
+    self.clear_type()
+    self.clear_params()
+
+  def OutputUnchecked(self, out):
+    out.putVarInt32(10)
+    out.putPrefixedString(self.name_)
+    out.putVarInt32(18)
+    out.putVarInt32(self.type_.ByteSize())
+    self.type_.OutputUnchecked(out)
+    if (self.has_params_):
+      out.putVarInt32(26)
+      out.putVarInt32(self.params_.ByteSize())
+      self.params_.OutputUnchecked(out)
+
+  def OutputPartial(self, out):
+    if (self.has_name_):
+      out.putVarInt32(10)
+      out.putPrefixedString(self.name_)
+    if (self.has_type_):
+      out.putVarInt32(18)
+      out.putVarInt32(self.type_.ByteSizePartial())
+      self.type_.OutputPartial(out)
+    if (self.has_params_):
+      out.putVarInt32(26)
+      out.putVarInt32(self.params_.ByteSizePartial())
+      self.params_.OutputPartial(out)
+
+  def TryMerge(self, d):
+    while d.avail() > 0:
+      tt = d.getVarInt32()
+      if tt == 10:
+        self.set_name(d.getPrefixedString())
+        continue
+      if tt == 18:
+        length = d.getVarInt32()
+        tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length)
+        d.skip(length)
+        self.mutable_type().TryMerge(tmp)
+        continue
+      if tt == 26:
+        length = d.getVarInt32()
+        tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length)
+        d.skip(length)
+        self.mutable_params().TryMerge(tmp)
+        continue
+
+
+      if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError
+      d.skipData(tt)
+
+
+  def __str__(self, prefix="", printElemNumber=0):
+    res=""
+    if self.has_name_: res+=prefix+("name: %s\n" % self.DebugFormatString(self.name_))
+    if self.has_type_:
+      res+=prefix+"type <\n"
+      res+=self.type_.__str__(prefix + "  ", printElemNumber)
+      res+=prefix+">\n"
+    if self.has_params_:
+      res+=prefix+"params <\n"
+      res+=self.params_.__str__(prefix + "  ", printElemNumber)
+      res+=prefix+">\n"
+    return res
+
+
+  def _BuildTagLookupTable(sparse, maxtag, default=None):
+    return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)])
+
+  kname = 1
+  ktype = 2
+  kparams = 3
+
+  _TEXT = _BuildTagLookupTable({
+    0: "ErrorCode",
+    1: "name",
+    2: "type",
+    3: "params",
+  }, 3)
+
+  _TYPES = _BuildTagLookupTable({
+    0: ProtocolBuffer.Encoder.NUMERIC,
+    1: ProtocolBuffer.Encoder.STRING,
+    2: ProtocolBuffer.Encoder.STRING,
+    3: ProtocolBuffer.Encoder.STRING,
+  }, 3, ProtocolBuffer.Encoder.MAX_TYPE)
+
+
+  _STYLE = """"""
+  _STYLE_CONTENT_TYPE = """"""
+  _PROTO_DESCRIPTOR_NAME = 'apphosting.FacetRequest'
+class FacetRefinement_Range(ProtocolBuffer.ProtocolMessage):
+  has_start_ = 0
+  start_ = ""
+  has_end_ = 0
+  end_ = ""
+
+  def __init__(self, contents=None):
+    if contents is not None: self.MergeFromString(contents)
+
+  def start(self): return self.start_
+
+  def set_start(self, x):
+    self.has_start_ = 1
+    self.start_ = x
+
+  def clear_start(self):
+    if self.has_start_:
+      self.has_start_ = 0
+      self.start_ = ""
+
+  def has_start(self): return self.has_start_
+
+  def end(self): return self.end_
+
+  def set_end(self, x):
+    self.has_end_ = 1
+    self.end_ = x
+
+  def clear_end(self):
+    if self.has_end_:
+      self.has_end_ = 0
+      self.end_ = ""
+
+  def has_end(self): return self.has_end_
+
+
+  def MergeFrom(self, x):
+    assert x is not self
+    if (x.has_start()): self.set_start(x.start())
+    if (x.has_end()): self.set_end(x.end())
+
+  def Equals(self, x):
+    if x is self: return 1
+    if self.has_start_ != x.has_start_: return 0
+    if self.has_start_ and self.start_ != x.start_: return 0
+    if self.has_end_ != x.has_end_: return 0
+    if self.has_end_ and self.end_ != x.end_: return 0
+    return 1
+
+  def IsInitialized(self, debug_strs=None):
+    initialized = 1
+    return initialized
+
+  def ByteSize(self):
+    n = 0
+    if (self.has_start_): n += 1 + self.lengthString(len(self.start_))
+    if (self.has_end_): n += 1 + self.lengthString(len(self.end_))
+    return n
+
+  def ByteSizePartial(self):
+    n = 0
+    if (self.has_start_): n += 1 + self.lengthString(len(self.start_))
+    if (self.has_end_): n += 1 + self.lengthString(len(self.end_))
+    return n
+
+  def Clear(self):
+    self.clear_start()
+    self.clear_end()
+
+  def OutputUnchecked(self, out):
+    if (self.has_start_):
+      out.putVarInt32(10)
+      out.putPrefixedString(self.start_)
+    if (self.has_end_):
+      out.putVarInt32(18)
+      out.putPrefixedString(self.end_)
+
+  def OutputPartial(self, out):
+    if (self.has_start_):
+      out.putVarInt32(10)
+      out.putPrefixedString(self.start_)
+    if (self.has_end_):
+      out.putVarInt32(18)
+      out.putPrefixedString(self.end_)
+
+  def TryMerge(self, d):
+    while d.avail() > 0:
+      tt = d.getVarInt32()
+      if tt == 10:
+        self.set_start(d.getPrefixedString())
+        continue
+      if tt == 18:
+        self.set_end(d.getPrefixedString())
+        continue
+
+
+      if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError
+      d.skipData(tt)
+
+
+  def __str__(self, prefix="", printElemNumber=0):
+    res=""
+    if self.has_start_: res+=prefix+("start: %s\n" % self.DebugFormatString(self.start_))
+    if self.has_end_: res+=prefix+("end: %s\n" % self.DebugFormatString(self.end_))
+    return res
+
+
+  def _BuildTagLookupTable(sparse, maxtag, default=None):
+    return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)])
+
+  kstart = 1
+  kend = 2
+
+  _TEXT = _BuildTagLookupTable({
+    0: "ErrorCode",
+    1: "start",
+    2: "end",
+  }, 2)
+
+  _TYPES = _BuildTagLookupTable({
+    0: ProtocolBuffer.Encoder.NUMERIC,
+    1: ProtocolBuffer.Encoder.STRING,
+    2: ProtocolBuffer.Encoder.STRING,
+  }, 2, ProtocolBuffer.Encoder.MAX_TYPE)
+
+
+  _STYLE = """"""
+  _STYLE_CONTENT_TYPE = """"""
+  _PROTO_DESCRIPTOR_NAME = 'apphosting.FacetRefinement_Range'
+class FacetRefinement(ProtocolBuffer.ProtocolMessage):
+  has_name_ = 0
+  name_ = ""
+  has_type_ = 0
+  has_value_ = 0
+  value_ = ""
+  has_range_ = 0
+  range_ = None
+
+  def __init__(self, contents=None):
+    self.type_ = ContentType()
+    self.lazy_init_lock_ = thread.allocate_lock()
+    if contents is not None: self.MergeFromString(contents)
+
+  def name(self): return self.name_
+
+  def set_name(self, x):
+    self.has_name_ = 1
+    self.name_ = x
+
+  def clear_name(self):
+    if self.has_name_:
+      self.has_name_ = 0
+      self.name_ = ""
+
+  def has_name(self): return self.has_name_
+
+  def type(self): return self.type_
+
+  def mutable_type(self): self.has_type_ = 1; return self.type_
+
+  def clear_type(self):self.has_type_ = 0; self.type_.Clear()
+
+  def has_type(self): return self.has_type_
+
+  def value(self): return self.value_
+
+  def set_value(self, x):
+    self.has_value_ = 1
+    self.value_ = x
+
+  def clear_value(self):
+    if self.has_value_:
+      self.has_value_ = 0
+      self.value_ = ""
+
+  def has_value(self): return self.has_value_
+
+  def range(self):
+    if self.range_ is None:
+      self.lazy_init_lock_.acquire()
+      try:
+        if self.range_ is None: self.range_ = FacetRefinement_Range()
+      finally:
+        self.lazy_init_lock_.release()
+    return self.range_
+
+  def mutable_range(self): self.has_range_ = 1; return self.range()
+
+  def clear_range(self):
+
+    if self.has_range_:
+      self.has_range_ = 0;
+      if self.range_ is not None: self.range_.Clear()
+
+  def has_range(self): return self.has_range_
+
+
+  def MergeFrom(self, x):
+    assert x is not self
+    if (x.has_name()): self.set_name(x.name())
+    if (x.has_type()): self.mutable_type().MergeFrom(x.type())
+    if (x.has_value()): self.set_value(x.value())
+    if (x.has_range()): self.mutable_range().MergeFrom(x.range())
+
+  def Equals(self, x):
+    if x is self: return 1
+    if self.has_name_ != x.has_name_: return 0
+    if self.has_name_ and self.name_ != x.name_: return 0
+    if self.has_type_ != x.has_type_: return 0
+    if self.has_type_ and self.type_ != x.type_: return 0
+    if self.has_value_ != x.has_value_: return 0
+    if self.has_value_ and self.value_ != x.value_: return 0
+    if self.has_range_ != x.has_range_: return 0
+    if self.has_range_ and self.range_ != x.range_: return 0
+    return 1
+
+  def IsInitialized(self, debug_strs=None):
+    initialized = 1
+    if (not self.has_name_):
+      initialized = 0
+      if debug_strs is not None:
+        debug_strs.append('Required field: name not set.')
+    if (not self.has_type_):
+      initialized = 0
+      if debug_strs is not None:
+        debug_strs.append('Required field: type not set.')
+    elif not self.type_.IsInitialized(debug_strs): initialized = 0
+    if (self.has_range_ and not self.range_.IsInitialized(debug_strs)): initialized = 0
+    return initialized
+
+  def ByteSize(self):
+    n = 0
+    n += self.lengthString(len(self.name_))
+    n += self.lengthString(self.type_.ByteSize())
+    if (self.has_value_): n += 1 + self.lengthString(len(self.value_))
+    if (self.has_range_): n += 1 + self.lengthString(self.range_.ByteSize())
+    return n + 2
+
+  def ByteSizePartial(self):
+    n = 0
+    if (self.has_name_):
+      n += 1
+      n += self.lengthString(len(self.name_))
+    if (self.has_type_):
+      n += 1
+      n += self.lengthString(self.type_.ByteSizePartial())
+    if (self.has_value_): n += 1 + self.lengthString(len(self.value_))
+    if (self.has_range_): n += 1 + self.lengthString(self.range_.ByteSizePartial())
+    return n
+
+  def Clear(self):
+    self.clear_name()
+    self.clear_type()
+    self.clear_value()
+    self.clear_range()
+
+  def OutputUnchecked(self, out):
+    out.putVarInt32(10)
+    out.putPrefixedString(self.name_)
+    out.putVarInt32(18)
+    out.putVarInt32(self.type_.ByteSize())
+    self.type_.OutputUnchecked(out)
+    if (self.has_value_):
+      out.putVarInt32(26)
+      out.putPrefixedString(self.value_)
+    if (self.has_range_):
+      out.putVarInt32(34)
+      out.putVarInt32(self.range_.ByteSize())
+      self.range_.OutputUnchecked(out)
+
+  def OutputPartial(self, out):
+    if (self.has_name_):
+      out.putVarInt32(10)
+      out.putPrefixedString(self.name_)
+    if (self.has_type_):
+      out.putVarInt32(18)
+      out.putVarInt32(self.type_.ByteSizePartial())
+      self.type_.OutputPartial(out)
+    if (self.has_value_):
+      out.putVarInt32(26)
+      out.putPrefixedString(self.value_)
+    if (self.has_range_):
+      out.putVarInt32(34)
+      out.putVarInt32(self.range_.ByteSizePartial())
+      self.range_.OutputPartial(out)
+
+  def TryMerge(self, d):
+    while d.avail() > 0:
+      tt = d.getVarInt32()
+      if tt == 10:
+        self.set_name(d.getPrefixedString())
+        continue
+      if tt == 18:
+        length = d.getVarInt32()
+        tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length)
+        d.skip(length)
+        self.mutable_type().TryMerge(tmp)
+        continue
+      if tt == 26:
+        self.set_value(d.getPrefixedString())
+        continue
+      if tt == 34:
+        length = d.getVarInt32()
+        tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length)
+        d.skip(length)
+        self.mutable_range().TryMerge(tmp)
+        continue
+
+
+      if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError
+      d.skipData(tt)
+
+
+  def __str__(self, prefix="", printElemNumber=0):
+    res=""
+    if self.has_name_: res+=prefix+("name: %s\n" % self.DebugFormatString(self.name_))
+    if self.has_type_:
+      res+=prefix+"type <\n"
+      res+=self.type_.__str__(prefix + "  ", printElemNumber)
+      res+=prefix+">\n"
+    if self.has_value_: res+=prefix+("value: %s\n" % self.DebugFormatString(self.value_))
+    if self.has_range_:
+      res+=prefix+"range <\n"
+      res+=self.range_.__str__(prefix + "  ", printElemNumber)
+      res+=prefix+">\n"
+    return res
+
+
+  def _BuildTagLookupTable(sparse, maxtag, default=None):
+    return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)])
+
+  kname = 1
+  ktype = 2
+  kvalue = 3
+  krange = 4
+
+  _TEXT = _BuildTagLookupTable({
+    0: "ErrorCode",
+    1: "name",
+    2: "type",
+    3: "value",
+    4: "range",
+  }, 4)
+
+  _TYPES = _BuildTagLookupTable({
+    0: ProtocolBuffer.Encoder.NUMERIC,
+    1: ProtocolBuffer.Encoder.STRING,
+    2: ProtocolBuffer.Encoder.STRING,
+    3: ProtocolBuffer.Encoder.STRING,
+    4: ProtocolBuffer.Encoder.STRING,
+  }, 4, ProtocolBuffer.Encoder.MAX_TYPE)
+
+
+  _STYLE = """"""
+  _STYLE_CONTENT_TYPE = """"""
+  _PROTO_DESCRIPTOR_NAME = 'apphosting.FacetRefinement'
 class SearchParams(ProtocolBuffer.ProtocolMessage):
 
 
@@ -4068,12 +5066,18 @@
   keys_only_ = 0
   has_parsing_mode_ = 0
   parsing_mode_ = 0
-  has_faceted_search_ = 0
-  faceted_search_ = 0
+  has_auto_discover_facet_count_ = 0
+  auto_discover_facet_count_ = 0
+  has_facet_auto_detect_param_ = 0
+  facet_auto_detect_param_ = None
+  has_facet_depth_ = 0
+  facet_depth_ = 1000
 
   def __init__(self, contents=None):
     self.index_spec_ = IndexSpec()
     self.sort_spec_ = []
+    self.include_facet_ = []
+    self.facet_refinement_ = []
     self.lazy_init_lock_ = thread.allocate_lock()
     if contents is not None: self.MergeFromString(contents)
 
@@ -4243,18 +5247,82 @@
 
   def has_parsing_mode(self): return self.has_parsing_mode_
 
-  def faceted_search(self): return self.faceted_search_
+  def auto_discover_facet_count(self): return self.auto_discover_facet_count_
 
-  def set_faceted_search(self, x):
-    self.has_faceted_search_ = 1
-    self.faceted_search_ = x
+  def set_auto_discover_facet_count(self, x):
+    self.has_auto_discover_facet_count_ = 1
+    self.auto_discover_facet_count_ = x
 
-  def clear_faceted_search(self):
-    if self.has_faceted_search_:
-      self.has_faceted_search_ = 0
-      self.faceted_search_ = 0
+  def clear_auto_discover_facet_count(self):
+    if self.has_auto_discover_facet_count_:
+      self.has_auto_discover_facet_count_ = 0
+      self.auto_discover_facet_count_ = 0
 
-  def has_faceted_search(self): return self.has_faceted_search_
+  def has_auto_discover_facet_count(self): return self.has_auto_discover_facet_count_
+
+  def include_facet_size(self): return len(self.include_facet_)
+  def include_facet_list(self): return self.include_facet_
+
+  def include_facet(self, i):
+    return self.include_facet_[i]
+
+  def mutable_include_facet(self, i):
+    return self.include_facet_[i]
+
+  def add_include_facet(self):
+    x = FacetRequest()
+    self.include_facet_.append(x)
+    return x
+
+  def clear_include_facet(self):
+    self.include_facet_ = []
+  def facet_refinement_size(self): return len(self.facet_refinement_)
+  def facet_refinement_list(self): return self.facet_refinement_
+
+  def facet_refinement(self, i):
+    return self.facet_refinement_[i]
+
+  def mutable_facet_refinement(self, i):
+    return self.facet_refinement_[i]
+
+  def add_facet_refinement(self):
+    x = FacetRefinement()
+    self.facet_refinement_.append(x)
+    return x
+
+  def clear_facet_refinement(self):
+    self.facet_refinement_ = []
+  def facet_auto_detect_param(self):
+    if self.facet_auto_detect_param_ is None:
+      self.lazy_init_lock_.acquire()
+      try:
+        if self.facet_auto_detect_param_ is None: self.facet_auto_detect_param_ = FacetAutoDetectParam()
+      finally:
+        self.lazy_init_lock_.release()
+    return self.facet_auto_detect_param_
+
+  def mutable_facet_auto_detect_param(self): self.has_facet_auto_detect_param_ = 1; return self.facet_auto_detect_param()
+
+  def clear_facet_auto_detect_param(self):
+
+    if self.has_facet_auto_detect_param_:
+      self.has_facet_auto_detect_param_ = 0;
+      if self.facet_auto_detect_param_ is not None: self.facet_auto_detect_param_.Clear()
+
+  def has_facet_auto_detect_param(self): return self.has_facet_auto_detect_param_
+
+  def facet_depth(self): return self.facet_depth_
+
+  def set_facet_depth(self, x):
+    self.has_facet_depth_ = 1
+    self.facet_depth_ = x
+
+  def clear_facet_depth(self):
+    if self.has_facet_depth_:
+      self.has_facet_depth_ = 0
+      self.facet_depth_ = 1000
+
+  def has_facet_depth(self): return self.has_facet_depth_
 
 
   def MergeFrom(self, x):
@@ -4271,7 +5339,11 @@
     if (x.has_field_spec()): self.mutable_field_spec().MergeFrom(x.field_spec())
     if (x.has_keys_only()): self.set_keys_only(x.keys_only())
     if (x.has_parsing_mode()): self.set_parsing_mode(x.parsing_mode())
-    if (x.has_faceted_search()): self.set_faceted_search(x.faceted_search())
+    if (x.has_auto_discover_facet_count()): self.set_auto_discover_facet_count(x.auto_discover_facet_count())
+    for i in xrange(x.include_facet_size()): self.add_include_facet().CopyFrom(x.include_facet(i))
+    for i in xrange(x.facet_refinement_size()): self.add_facet_refinement().CopyFrom(x.facet_refinement(i))
+    if (x.has_facet_auto_detect_param()): self.mutable_facet_auto_detect_param().MergeFrom(x.facet_auto_detect_param())
+    if (x.has_facet_depth()): self.set_facet_depth(x.facet_depth())
 
   def Equals(self, x):
     if x is self: return 1
@@ -4300,8 +5372,18 @@
     if self.has_keys_only_ and self.keys_only_ != x.keys_only_: return 0
     if self.has_parsing_mode_ != x.has_parsing_mode_: return 0
     if self.has_parsing_mode_ and self.parsing_mode_ != x.parsing_mode_: return 0
-    if self.has_faceted_search_ != x.has_faceted_search_: return 0
-    if self.has_faceted_search_ and self.faceted_search_ != x.faceted_search_: return 0
+    if self.has_auto_discover_facet_count_ != x.has_auto_discover_facet_count_: return 0
+    if self.has_auto_discover_facet_count_ and self.auto_discover_facet_count_ != x.auto_discover_facet_count_: return 0
+    if len(self.include_facet_) != len(x.include_facet_): return 0
+    for e1, e2 in zip(self.include_facet_, x.include_facet_):
+      if e1 != e2: return 0
+    if len(self.facet_refinement_) != len(x.facet_refinement_): return 0
+    for e1, e2 in zip(self.facet_refinement_, x.facet_refinement_):
+      if e1 != e2: return 0
+    if self.has_facet_auto_detect_param_ != x.has_facet_auto_detect_param_: return 0
+    if self.has_facet_auto_detect_param_ and self.facet_auto_detect_param_ != x.facet_auto_detect_param_: return 0
+    if self.has_facet_depth_ != x.has_facet_depth_: return 0
+    if self.has_facet_depth_ and self.facet_depth_ != x.facet_depth_: return 0
     return 1
 
   def IsInitialized(self, debug_strs=None):
@@ -4319,6 +5401,11 @@
       if not p.IsInitialized(debug_strs): initialized=0
     if (self.has_scorer_spec_ and not self.scorer_spec_.IsInitialized(debug_strs)): initialized = 0
     if (self.has_field_spec_ and not self.field_spec_.IsInitialized(debug_strs)): initialized = 0
+    for p in self.include_facet_:
+      if not p.IsInitialized(debug_strs): initialized=0
+    for p in self.facet_refinement_:
+      if not p.IsInitialized(debug_strs): initialized=0
+    if (self.has_facet_auto_detect_param_ and not self.facet_auto_detect_param_.IsInitialized(debug_strs)): initialized = 0
     return initialized
 
   def ByteSize(self):
@@ -4336,7 +5423,13 @@
     if (self.has_field_spec_): n += 1 + self.lengthString(self.field_spec_.ByteSize())
     if (self.has_keys_only_): n += 2
     if (self.has_parsing_mode_): n += 1 + self.lengthVarInt64(self.parsing_mode_)
-    if (self.has_faceted_search_): n += 2
+    if (self.has_auto_discover_facet_count_): n += 1 + self.lengthVarInt64(self.auto_discover_facet_count_)
+    n += 2 * len(self.include_facet_)
+    for i in xrange(len(self.include_facet_)): n += self.lengthString(self.include_facet_[i].ByteSize())
+    n += 2 * len(self.facet_refinement_)
+    for i in xrange(len(self.facet_refinement_)): n += self.lengthString(self.facet_refinement_[i].ByteSize())
+    if (self.has_facet_auto_detect_param_): n += 2 + self.lengthString(self.facet_auto_detect_param_.ByteSize())
+    if (self.has_facet_depth_): n += 2 + self.lengthVarInt64(self.facet_depth_)
     return n + 2
 
   def ByteSizePartial(self):
@@ -4358,7 +5451,13 @@
     if (self.has_field_spec_): n += 1 + self.lengthString(self.field_spec_.ByteSizePartial())
     if (self.has_keys_only_): n += 2
     if (self.has_parsing_mode_): n += 1 + self.lengthVarInt64(self.parsing_mode_)
-    if (self.has_faceted_search_): n += 2
+    if (self.has_auto_discover_facet_count_): n += 1 + self.lengthVarInt64(self.auto_discover_facet_count_)
+    n += 2 * len(self.include_facet_)
+    for i in xrange(len(self.include_facet_)): n += self.lengthString(self.include_facet_[i].ByteSizePartial())
+    n += 2 * len(self.facet_refinement_)
+    for i in xrange(len(self.facet_refinement_)): n += self.lengthString(self.facet_refinement_[i].ByteSizePartial())
+    if (self.has_facet_auto_detect_param_): n += 2 + self.lengthString(self.facet_auto_detect_param_.ByteSizePartial())
+    if (self.has_facet_depth_): n += 2 + self.lengthVarInt64(self.facet_depth_)
     return n
 
   def Clear(self):
@@ -4374,7 +5473,11 @@
     self.clear_field_spec()
     self.clear_keys_only()
     self.clear_parsing_mode()
-    self.clear_faceted_search()
+    self.clear_auto_discover_facet_count()
+    self.clear_include_facet()
+    self.clear_facet_refinement()
+    self.clear_facet_auto_detect_param()
+    self.clear_facet_depth()
 
   def OutputUnchecked(self, out):
     out.putVarInt32(10)
@@ -4415,9 +5518,24 @@
     if (self.has_parsing_mode_):
       out.putVarInt32(104)
       out.putVarInt32(self.parsing_mode_)
-    if (self.has_faceted_search_):
-      out.putVarInt32(112)
-      out.putBoolean(self.faceted_search_)
+    if (self.has_auto_discover_facet_count_):
+      out.putVarInt32(120)
+      out.putVarInt32(self.auto_discover_facet_count_)
+    for i in xrange(len(self.include_facet_)):
+      out.putVarInt32(130)
+      out.putVarInt32(self.include_facet_[i].ByteSize())
+      self.include_facet_[i].OutputUnchecked(out)
+    for i in xrange(len(self.facet_refinement_)):
+      out.putVarInt32(138)
+      out.putVarInt32(self.facet_refinement_[i].ByteSize())
+      self.facet_refinement_[i].OutputUnchecked(out)
+    if (self.has_facet_auto_detect_param_):
+      out.putVarInt32(146)
+      out.putVarInt32(self.facet_auto_detect_param_.ByteSize())
+      self.facet_auto_detect_param_.OutputUnchecked(out)
+    if (self.has_facet_depth_):
+      out.putVarInt32(152)
+      out.putVarInt32(self.facet_depth_)
 
   def OutputPartial(self, out):
     if (self.has_index_spec_):
@@ -4460,9 +5578,24 @@
     if (self.has_parsing_mode_):
       out.putVarInt32(104)
       out.putVarInt32(self.parsing_mode_)
-    if (self.has_faceted_search_):
-      out.putVarInt32(112)
-      out.putBoolean(self.faceted_search_)
+    if (self.has_auto_discover_facet_count_):
+      out.putVarInt32(120)
+      out.putVarInt32(self.auto_discover_facet_count_)
+    for i in xrange(len(self.include_facet_)):
+      out.putVarInt32(130)
+      out.putVarInt32(self.include_facet_[i].ByteSizePartial())
+      self.include_facet_[i].OutputPartial(out)
+    for i in xrange(len(self.facet_refinement_)):
+      out.putVarInt32(138)
+      out.putVarInt32(self.facet_refinement_[i].ByteSizePartial())
+      self.facet_refinement_[i].OutputPartial(out)
+    if (self.has_facet_auto_detect_param_):
+      out.putVarInt32(146)
+      out.putVarInt32(self.facet_auto_detect_param_.ByteSizePartial())
+      self.facet_auto_detect_param_.OutputPartial(out)
+    if (self.has_facet_depth_):
+      out.putVarInt32(152)
+      out.putVarInt32(self.facet_depth_)
 
   def TryMerge(self, d):
     while d.avail() > 0:
@@ -4515,8 +5648,29 @@
       if tt == 104:
         self.set_parsing_mode(d.getVarInt32())
         continue
-      if tt == 112:
-        self.set_faceted_search(d.getBoolean())
+      if tt == 120:
+        self.set_auto_discover_facet_count(d.getVarInt32())
+        continue
+      if tt == 130:
+        length = d.getVarInt32()
+        tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length)
+        d.skip(length)
+        self.add_include_facet().TryMerge(tmp)
+        continue
+      if tt == 138:
+        length = d.getVarInt32()
+        tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length)
+        d.skip(length)
+        self.add_facet_refinement().TryMerge(tmp)
+        continue
+      if tt == 146:
+        length = d.getVarInt32()
+        tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length)
+        d.skip(length)
+        self.mutable_facet_auto_detect_param().TryMerge(tmp)
+        continue
+      if tt == 152:
+        self.set_facet_depth(d.getVarInt32())
         continue
 
 
@@ -4554,7 +5708,28 @@
       res+=prefix+">\n"
     if self.has_keys_only_: res+=prefix+("keys_only: %s\n" % self.DebugFormatBool(self.keys_only_))
     if self.has_parsing_mode_: res+=prefix+("parsing_mode: %s\n" % self.DebugFormatInt32(self.parsing_mode_))
-    if self.has_faceted_search_: res+=prefix+("faceted_search: %s\n" % self.DebugFormatBool(self.faceted_search_))
+    if self.has_auto_discover_facet_count_: res+=prefix+("auto_discover_facet_count: %s\n" % self.DebugFormatInt32(self.auto_discover_facet_count_))
+    cnt=0
+    for e in self.include_facet_:
+      elm=""
+      if printElemNumber: elm="(%d)" % cnt
+      res+=prefix+("include_facet%s <\n" % elm)
+      res+=e.__str__(prefix + "  ", printElemNumber)
+      res+=prefix+">\n"
+      cnt+=1
+    cnt=0
+    for e in self.facet_refinement_:
+      elm=""
+      if printElemNumber: elm="(%d)" % cnt
+      res+=prefix+("facet_refinement%s <\n" % elm)
+      res+=e.__str__(prefix + "  ", printElemNumber)
+      res+=prefix+">\n"
+      cnt+=1
+    if self.has_facet_auto_detect_param_:
+      res+=prefix+"facet_auto_detect_param <\n"
+      res+=self.facet_auto_detect_param_.__str__(prefix + "  ", printElemNumber)
+      res+=prefix+">\n"
+    if self.has_facet_depth_: res+=prefix+("facet_depth: %s\n" % self.DebugFormatInt32(self.facet_depth_))
     return res
 
 
@@ -4573,7 +5748,11 @@
   kfield_spec = 10
   kkeys_only = 12
   kparsing_mode = 13
-  kfaceted_search = 14
+  kauto_discover_facet_count = 15
+  kinclude_facet = 16
+  kfacet_refinement = 17
+  kfacet_auto_detect_param = 18
+  kfacet_depth = 19
 
   _TEXT = _BuildTagLookupTable({
     0: "ErrorCode",
@@ -4589,8 +5768,12 @@
     11: "offset",
     12: "keys_only",
     13: "parsing_mode",
-    14: "faceted_search",
-  }, 14)
+    15: "auto_discover_facet_count",
+    16: "include_facet",
+    17: "facet_refinement",
+    18: "facet_auto_detect_param",
+    19: "facet_depth",
+  }, 19)
 
   _TYPES = _BuildTagLookupTable({
     0: ProtocolBuffer.Encoder.NUMERIC,
@@ -4606,8 +5789,12 @@
     11: ProtocolBuffer.Encoder.NUMERIC,
     12: ProtocolBuffer.Encoder.NUMERIC,
     13: ProtocolBuffer.Encoder.NUMERIC,
-    14: ProtocolBuffer.Encoder.NUMERIC,
-  }, 14, ProtocolBuffer.Encoder.MAX_TYPE)
+    15: ProtocolBuffer.Encoder.NUMERIC,
+    16: ProtocolBuffer.Encoder.STRING,
+    17: ProtocolBuffer.Encoder.STRING,
+    18: ProtocolBuffer.Encoder.STRING,
+    19: ProtocolBuffer.Encoder.NUMERIC,
+  }, 19, ProtocolBuffer.Encoder.MAX_TYPE)
 
 
   _STYLE = """"""
@@ -5631,4 +6818,4 @@
 if _extension_runtime:
   pass
 
-__all__ = ['SearchServiceError','RequestStatus','IndexSpec','IndexMetadata_Storage','IndexMetadata','IndexDocumentParams','IndexDocumentRequest','IndexDocumentResponse','DeleteDocumentParams','DeleteDocumentRequest','DeleteDocumentResponse','ListDocumentsParams','ListDocumentsRequest','ListDocumentsResponse','ListIndexesParams','ListIndexesRequest','ListIndexesResponse','DeleteSchemaParams','DeleteSchemaRequest','DeleteSchemaResponse','SortSpec','ScorerSpec','FieldSpec','FieldSpec_Expression','SearchParams','SearchRequest','FacetResultValue','FacetResult','SearchResult','SearchResponse']
+__all__ = ['SearchServiceError','RequestStatus','IndexSpec','IndexMetadata_Storage','IndexMetadata','IndexDocumentParams','IndexDocumentRequest','IndexDocumentResponse','DeleteDocumentParams','DeleteDocumentRequest','DeleteDocumentResponse','ListDocumentsParams','ListDocumentsRequest','ListDocumentsResponse','ListIndexesParams','ListIndexesRequest','ListIndexesResponse','DeleteSchemaParams','DeleteSchemaRequest','DeleteSchemaResponse','SortSpec','ScorerSpec','FieldSpec','FieldSpec_Expression','FacetRange','FacetRequestParam','FacetAutoDetectParam','FacetRequest','FacetRefinement_Range','FacetRefinement','SearchParams','SearchRequest','FacetResultValue','FacetResult','SearchResult','SearchResponse']
diff --git a/google/appengine/api/taskqueue/taskqueue_stub.py b/google/appengine/api/taskqueue/taskqueue_stub.py
index c25ae30..d3b2500 100644
--- a/google/appengine/api/taskqueue/taskqueue_stub.py
+++ b/google/appengine/api/taskqueue/taskqueue_stub.py
@@ -1739,14 +1739,14 @@
     eta = old_task.eta_usec()
     if not self._RemoveTaskFromIndex(
         self._sorted_by_eta, (eta, name, None), old_task):
-      return taskqueue_service_pb.TaskQueueServiceError.INTERNAL_ERRROR
+      return taskqueue_service_pb.TaskQueueServiceError.INTERNAL_ERROR
 
 
     if old_task.has_tag():
       tag = old_task.tag()
       if not self._RemoveTaskFromIndex(
           self._sorted_by_tag, (tag, eta, name, None), old_task):
-        return taskqueue_service_pb.TaskQueueServiceError.INTERNAL_ERRROR
+        return taskqueue_service_pb.TaskQueueServiceError.INTERNAL_ERROR
 
     return taskqueue_service_pb.TaskQueueServiceError.OK
 
diff --git a/google/appengine/api/validation.py b/google/appengine/api/validation.py
index 5130230..0ffc3f8 100644
--- a/google/appengine/api/validation.py
+++ b/google/appengine/api/validation.py
@@ -985,7 +985,7 @@
       regex_list.append(self.__AsString(item))
 
     if sequence:
-      return '|'.join('(?:%s)' % item for item in regex_list)
+      return '|'.join('%s' % item for item in regex_list)
     else:
       return regex_list[0]
 
diff --git a/google/appengine/datastore/datastore_stub_util.py b/google/appengine/datastore/datastore_stub_util.py
index d9b1b71..108f32c 100644
--- a/google/appengine/datastore/datastore_stub_util.py
+++ b/google/appengine/datastore/datastore_stub_util.py
@@ -2885,8 +2885,11 @@
 
 
     if req.allow_deferred() and req.key_size() > _MAXIMUM_RESULTS:
-      keys_to_get = req.key_list()[:_MAXIMUM_RESULTS]
-      deferred_keys = req.key_list()[_MAXIMUM_RESULTS:]
+
+
+
+      keys_to_get = req.key_list()[-_MAXIMUM_RESULTS:]
+      deferred_keys = req.key_list()[:-_MAXIMUM_RESULTS]
       res.deferred_list().extend(deferred_keys)
     else:
 
diff --git a/google/appengine/datastore/datastore_v3_pb.py b/google/appengine/datastore/datastore_v3_pb.py
index 7470db3..54f30fa 100644
--- a/google/appengine/datastore/datastore_v3_pb.py
+++ b/google/appengine/datastore/datastore_v3_pb.py
@@ -40,34 +40,52 @@
 from google.appengine.datastore.snapshot_pb import *
 import google.appengine.datastore.snapshot_pb
 class InternalHeader(ProtocolBuffer.ProtocolMessage):
-  has_qos_ = 0
-  qos_ = ""
+  has_requesting_app_id_ = 0
+  requesting_app_id_ = ""
+  has_api_settings_ = 0
+  api_settings_ = ""
 
   def __init__(self, contents=None):
     if contents is not None: self.MergeFromString(contents)
 
-  def qos(self): return self.qos_
+  def requesting_app_id(self): return self.requesting_app_id_
 
-  def set_qos(self, x):
-    self.has_qos_ = 1
-    self.qos_ = x
+  def set_requesting_app_id(self, x):
+    self.has_requesting_app_id_ = 1
+    self.requesting_app_id_ = x
 
-  def clear_qos(self):
-    if self.has_qos_:
-      self.has_qos_ = 0
-      self.qos_ = ""
+  def clear_requesting_app_id(self):
+    if self.has_requesting_app_id_:
+      self.has_requesting_app_id_ = 0
+      self.requesting_app_id_ = ""
 
-  def has_qos(self): return self.has_qos_
+  def has_requesting_app_id(self): return self.has_requesting_app_id_
+
+  def api_settings(self): return self.api_settings_
+
+  def set_api_settings(self, x):
+    self.has_api_settings_ = 1
+    self.api_settings_ = x
+
+  def clear_api_settings(self):
+    if self.has_api_settings_:
+      self.has_api_settings_ = 0
+      self.api_settings_ = ""
+
+  def has_api_settings(self): return self.has_api_settings_
 
 
   def MergeFrom(self, x):
     assert x is not self
-    if (x.has_qos()): self.set_qos(x.qos())
+    if (x.has_requesting_app_id()): self.set_requesting_app_id(x.requesting_app_id())
+    if (x.has_api_settings()): self.set_api_settings(x.api_settings())
 
   def Equals(self, x):
     if x is self: return 1
-    if self.has_qos_ != x.has_qos_: return 0
-    if self.has_qos_ and self.qos_ != x.qos_: return 0
+    if self.has_requesting_app_id_ != x.has_requesting_app_id_: return 0
+    if self.has_requesting_app_id_ and self.requesting_app_id_ != x.requesting_app_id_: return 0
+    if self.has_api_settings_ != x.has_api_settings_: return 0
+    if self.has_api_settings_ and self.api_settings_ != x.api_settings_: return 0
     return 1
 
   def IsInitialized(self, debug_strs=None):
@@ -76,32 +94,44 @@
 
   def ByteSize(self):
     n = 0
-    if (self.has_qos_): n += 1 + self.lengthString(len(self.qos_))
+    if (self.has_requesting_app_id_): n += 1 + self.lengthString(len(self.requesting_app_id_))
+    if (self.has_api_settings_): n += 1 + self.lengthString(len(self.api_settings_))
     return n
 
   def ByteSizePartial(self):
     n = 0
-    if (self.has_qos_): n += 1 + self.lengthString(len(self.qos_))
+    if (self.has_requesting_app_id_): n += 1 + self.lengthString(len(self.requesting_app_id_))
+    if (self.has_api_settings_): n += 1 + self.lengthString(len(self.api_settings_))
     return n
 
   def Clear(self):
-    self.clear_qos()
+    self.clear_requesting_app_id()
+    self.clear_api_settings()
 
   def OutputUnchecked(self, out):
-    if (self.has_qos_):
-      out.putVarInt32(10)
-      out.putPrefixedString(self.qos_)
+    if (self.has_requesting_app_id_):
+      out.putVarInt32(18)
+      out.putPrefixedString(self.requesting_app_id_)
+    if (self.has_api_settings_):
+      out.putVarInt32(26)
+      out.putPrefixedString(self.api_settings_)
 
   def OutputPartial(self, out):
-    if (self.has_qos_):
-      out.putVarInt32(10)
-      out.putPrefixedString(self.qos_)
+    if (self.has_requesting_app_id_):
+      out.putVarInt32(18)
+      out.putPrefixedString(self.requesting_app_id_)
+    if (self.has_api_settings_):
+      out.putVarInt32(26)
+      out.putPrefixedString(self.api_settings_)
 
   def TryMerge(self, d):
     while d.avail() > 0:
       tt = d.getVarInt32()
-      if tt == 10:
-        self.set_qos(d.getPrefixedString())
+      if tt == 18:
+        self.set_requesting_app_id(d.getPrefixedString())
+        continue
+      if tt == 26:
+        self.set_api_settings(d.getPrefixedString())
         continue
 
 
@@ -111,24 +141,28 @@
 
   def __str__(self, prefix="", printElemNumber=0):
     res=""
-    if self.has_qos_: res+=prefix+("qos: %s\n" % self.DebugFormatString(self.qos_))
+    if self.has_requesting_app_id_: res+=prefix+("requesting_app_id: %s\n" % self.DebugFormatString(self.requesting_app_id_))
+    if self.has_api_settings_: res+=prefix+("api_settings: %s\n" % self.DebugFormatString(self.api_settings_))
     return res
 
 
   def _BuildTagLookupTable(sparse, maxtag, default=None):
     return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)])
 
-  kqos = 1
+  krequesting_app_id = 2
+  kapi_settings = 3
 
   _TEXT = _BuildTagLookupTable({
     0: "ErrorCode",
-    1: "qos",
-  }, 1)
+    2: "requesting_app_id",
+    3: "api_settings",
+  }, 3)
 
   _TYPES = _BuildTagLookupTable({
     0: ProtocolBuffer.Encoder.NUMERIC,
-    1: ProtocolBuffer.Encoder.STRING,
-  }, 1, ProtocolBuffer.Encoder.MAX_TYPE)
+    2: ProtocolBuffer.Encoder.STRING,
+    3: ProtocolBuffer.Encoder.STRING,
+  }, 3, ProtocolBuffer.Encoder.MAX_TYPE)
 
 
   _STYLE = """"""
@@ -2430,6 +2464,8 @@
   distinct_infix_size_ = 0
   has_entityfilter_ = 0
   entityfilter_ = None
+  has_plan_label_ = 0
+  plan_label_ = ""
 
   def __init__(self, contents=None):
     self.primaryscan_ = CompiledQuery_PrimaryScan()
@@ -2567,6 +2603,19 @@
 
   def has_entityfilter(self): return self.has_entityfilter_
 
+  def plan_label(self): return self.plan_label_
+
+  def set_plan_label(self, x):
+    self.has_plan_label_ = 1
+    self.plan_label_ = x
+
+  def clear_plan_label(self):
+    if self.has_plan_label_:
+      self.has_plan_label_ = 0
+      self.plan_label_ = ""
+
+  def has_plan_label(self): return self.has_plan_label_
+
 
   def MergeFrom(self, x):
     assert x is not self
@@ -2579,6 +2628,7 @@
     for i in xrange(x.property_name_size()): self.add_property_name(x.property_name(i))
     if (x.has_distinct_infix_size()): self.set_distinct_infix_size(x.distinct_infix_size())
     if (x.has_entityfilter()): self.mutable_entityfilter().MergeFrom(x.entityfilter())
+    if (x.has_plan_label()): self.set_plan_label(x.plan_label())
 
   def Equals(self, x):
     if x is self: return 1
@@ -2602,6 +2652,8 @@
     if self.has_distinct_infix_size_ and self.distinct_infix_size_ != x.distinct_infix_size_: return 0
     if self.has_entityfilter_ != x.has_entityfilter_: return 0
     if self.has_entityfilter_ and self.entityfilter_ != x.entityfilter_: return 0
+    if self.has_plan_label_ != x.has_plan_label_: return 0
+    if self.has_plan_label_ and self.plan_label_ != x.plan_label_: return 0
     return 1
 
   def IsInitialized(self, debug_strs=None):
@@ -2633,6 +2685,7 @@
     for i in xrange(len(self.property_name_)): n += self.lengthString(len(self.property_name_[i]))
     if (self.has_distinct_infix_size_): n += 2 + self.lengthVarInt64(self.distinct_infix_size_)
     if (self.has_entityfilter_): n += 2 + self.entityfilter_.ByteSize()
+    if (self.has_plan_label_): n += 2 + self.lengthString(len(self.plan_label_))
     return n + 4
 
   def ByteSizePartial(self):
@@ -2651,6 +2704,7 @@
     for i in xrange(len(self.property_name_)): n += self.lengthString(len(self.property_name_[i]))
     if (self.has_distinct_infix_size_): n += 2 + self.lengthVarInt64(self.distinct_infix_size_)
     if (self.has_entityfilter_): n += 2 + self.entityfilter_.ByteSizePartial()
+    if (self.has_plan_label_): n += 2 + self.lengthString(len(self.plan_label_))
     return n
 
   def Clear(self):
@@ -2663,6 +2717,7 @@
     self.clear_property_name()
     self.clear_distinct_infix_size()
     self.clear_entityfilter()
+    self.clear_plan_label()
 
   def OutputUnchecked(self, out):
     out.putVarInt32(11)
@@ -2694,6 +2749,9 @@
     if (self.has_distinct_infix_size_):
       out.putVarInt32(200)
       out.putVarInt32(self.distinct_infix_size_)
+    if (self.has_plan_label_):
+      out.putVarInt32(210)
+      out.putPrefixedString(self.plan_label_)
 
   def OutputPartial(self, out):
     if (self.has_primaryscan_):
@@ -2727,6 +2785,9 @@
     if (self.has_distinct_infix_size_):
       out.putVarInt32(200)
       out.putVarInt32(self.distinct_infix_size_)
+    if (self.has_plan_label_):
+      out.putVarInt32(210)
+      out.putPrefixedString(self.plan_label_)
 
   def TryMerge(self, d):
     while d.avail() > 0:
@@ -2761,6 +2822,9 @@
       if tt == 200:
         self.set_distinct_infix_size(d.getVarInt32())
         continue
+      if tt == 210:
+        self.set_plan_label(d.getPrefixedString())
+        continue
 
 
       if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError
@@ -2799,6 +2863,7 @@
       res+=prefix+"EntityFilter {\n"
       res+=self.entityfilter_.__str__(prefix + "  ", printElemNumber)
       res+=prefix+"}\n"
+    if self.has_plan_label_: res+=prefix+("plan_label: %s\n" % self.DebugFormatString(self.plan_label_))
     return res
 
 
@@ -2828,6 +2893,7 @@
   kEntityFilterdistinct = 14
   kEntityFilterkind = 17
   kEntityFilterancestor = 18
+  kplan_label = 26
 
   _TEXT = _BuildTagLookupTable({
     0: "ErrorCode",
@@ -2854,7 +2920,8 @@
     23: "end_postfix_value",
     24: "property_name",
     25: "distinct_infix_size",
-  }, 25)
+    26: "plan_label",
+  }, 26)
 
   _TYPES = _BuildTagLookupTable({
     0: ProtocolBuffer.Encoder.NUMERIC,
@@ -2881,7 +2948,8 @@
     23: ProtocolBuffer.Encoder.STRING,
     24: ProtocolBuffer.Encoder.STRING,
     25: ProtocolBuffer.Encoder.NUMERIC,
-  }, 25, ProtocolBuffer.Encoder.MAX_TYPE)
+    26: ProtocolBuffer.Encoder.STRING,
+  }, 26, ProtocolBuffer.Encoder.MAX_TYPE)
 
 
   _STYLE = """"""
diff --git a/google/appengine/datastore/datastore_v4_pb.py b/google/appengine/datastore/datastore_v4_pb.py
index 3dfcc9c..e806272 100644
--- a/google/appengine/datastore/datastore_v4_pb.py
+++ b/google/appengine/datastore/datastore_v4_pb.py
@@ -177,7 +177,7 @@
   _STYLE_CONTENT_TYPE = """"""
   _PROTO_DESCRIPTOR_NAME = 'apphosting.datastore.v4.Error'
   _SERIALIZED_DESCRIPTOR = array.array('B')
-  _SERIALIZED_DESCRIPTOR.fromstring(base64.decodestring("WidhcHBob3N0aW5nL2RhdGFzdG9yZS9kYXRhc3RvcmVfdjQucHJvdG8KHWFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkVycm9yc3oJRXJyb3JDb2RliwGSAQtCQURfUkVRVUVTVJgBAYwBiwGSARZDT05DVVJSRU5UX1RSQU5TQUNUSU9OmAECjAGLAZIBDklOVEVSTkFMX0VSUk9SmAEDjAGLAZIBCk5FRURfSU5ERViYAQSMAYsBkgEHVElNRU9VVJgBBYwBiwGSARFQRVJNSVNTSU9OX0RFTklFRJgBBowBiwGSAQ5CSUdUQUJMRV9FUlJPUpgBB4wBiwGSARxDT01NSVRURURfQlVUX1NUSUxMX0FQUExZSU5HmAEIjAGLAZIBE0NBUEFCSUxJVFlfRElTQUJMRUSYAQmMAYsBkgEVVFJZX0FMVEVSTkFURV9CQUNLRU5EmAEKjAGLAZIBEVNBRkVfVElNRV9UT09fT0xEmAELjAF0ugHPMgonYXBwaG9zdGluZy9kYXRhc3RvcmUvZGF0YXN0b3JlX3Y0LnByb3RvEhdhcHBob3N0aW5nLmRhdGFzdG9yZS52NBokYXBwaG9zdGluZy9kYXRhc3RvcmUvZW50aXR5X3Y0LnByb3RvIosCCgVFcnJvciKBAgoJRXJyb3JDb2RlEg8KC0JBRF9SRVFVRVNUEAESGgoWQ09OQ1VSUkVOVF9UUkFOU0FDVElPThACEhIKDklOVEVSTkFMX0VSUk9SEAMSDgoKTkVFRF9JTkRFWBAEEgsKB1RJTUVPVVQQBRIVChFQRVJNSVNTSU9OX0RFTklFRBAGEhIKDkJJR1RBQkxFX0VSUk9SEAcSIAocQ09NTUlUVEVEX0JVVF9TVElMTF9BUFBMWUlORxAIEhcKE0NBUEFCSUxJVFlfRElTQUJMRUQQCRIZChVUUllfQUxURVJOQVRFX0JBQ0tFTkQQChIVChFTQUZFX1RJTUVfVE9PX09MRBALIoYBCgxFbnRpdHlSZXN1bHQSLwoGZW50aXR5GAEgAigLMh8uYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuRW50aXR5Eg8KB3ZlcnNpb24YAiABKAMiNAoKUmVzdWx0VHlwZRIICgRGVUxMEAESDgoKUFJPSkVDVElPThACEgwKCEtFWV9PTkxZEAMi8QIKBVF1ZXJ5Ej8KCnByb2plY3Rpb24YAiADKAsyKy5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5Qcm9wZXJ0eUV4cHJlc3Npb24SNQoEa2luZBgDIAMoCzInLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LktpbmRFeHByZXNzaW9uEi8KBmZpbHRlchgEIAEoCzIfLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkZpbHRlchI1CgVvcmRlchgFIAMoCzImLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LlByb3BlcnR5T3JkZXISPAoIZ3JvdXBfYnkYBiADKAsyKi5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5Qcm9wZXJ0eVJlZmVyZW5jZRIUCgxzdGFydF9jdXJzb3IYByABKAwSEgoKZW5kX2N1cnNvchgIIAEoDBIRCgZvZmZzZXQYCiABKAU6ATASDQoFbGltaXQYCyABKAUiHgoOS2luZEV4cHJlc3Npb24SDAoEbmFtZRgBIAIoCSIhChFQcm9wZXJ0eVJlZmVyZW5jZRIMCgRuYW1lGAIgAigJItMBChJQcm9wZXJ0eUV4cHJlc3Npb24SPAoIcHJvcGVydHkYASACKAsyKi5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5Qcm9wZXJ0eVJlZmVyZW5jZRJdChRhZ2dyZWdhdGlvbl9mdW5jdGlvbhgCIAEoDjI/LmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LlByb3BlcnR5RXhwcmVzc2lvbi5BZ2dyZWdhdGlvbkZ1bmN0aW9uIiAKE0FnZ3JlZ2F0aW9uRnVuY3Rpb24SCQoFRklSU1QQASLJAQoNUHJvcGVydHlPcmRlchI8Cghwcm9wZXJ0eRgBIAIoCzIqLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LlByb3BlcnR5UmVmZXJlbmNlEk4KCWRpcmVjdGlvbhgCIAEoDjIwLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LlByb3BlcnR5T3JkZXIuRGlyZWN0aW9uOglBU0NFTkRJTkciKgoJRGlyZWN0aW9uEg0KCUFTQ0VORElORxABEg4KCkRFU0NFTkRJTkcQAiKOAQoGRmlsdGVyEkIKEGNvbXBvc2l0ZV9maWx0ZXIYASABKAsyKC5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5Db21wb3NpdGVGaWx0ZXISQAoPcHJvcGVydHlfZmlsdGVyGAIgASgLMicuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuUHJvcGVydHlGaWx0ZXIinAEKD0NvbXBvc2l0ZUZpbHRlchJDCghvcGVyYXRvchgBIAIoDjIxLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkNvbXBvc2l0ZUZpbHRlci5PcGVyYXRvchIvCgZmaWx0ZXIYAiADKAsyHy5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5GaWx0ZXIiEwoIT3BlcmF0b3ISBwoDQU5EEAEivgIKDlByb3BlcnR5RmlsdGVyEjwKCHByb3BlcnR5GAEgAigLMiouYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuUHJvcGVydHlSZWZlcmVuY2USQgoIb3BlcmF0b3IYAiACKA4yMC5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5Qcm9wZXJ0eUZpbHRlci5PcGVyYXRvchItCgV2YWx1ZRgDIAIoCzIeLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LlZhbHVlInsKCE9wZXJhdG9yEg0KCUxFU1NfVEhBThABEhYKEkxFU1NfVEhBTl9PUl9FUVVBTBACEhAKDEdSRUFURVJfVEhBThADEhkKFUdSRUFURVJfVEhBTl9PUl9FUVVBTBAEEgkKBUVRVUFMEAUSEAoMSEFTX0FOQ0VTVE9SEAsisAEKCEdxbFF1ZXJ5EhQKDHF1ZXJ5X3N0cmluZxgBIAIoCRIcCg1hbGxvd19saXRlcmFsGAIgASgIOgVmYWxzZRI2CghuYW1lX2FyZxgDIAMoCzIkLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkdxbFF1ZXJ5QXJnEjgKCm51bWJlcl9hcmcYBCADKAsyJC5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5HcWxRdWVyeUFyZyJaCgtHcWxRdWVyeUFyZxIMCgRuYW1lGAEgASgJEi0KBXZhbHVlGAIgASgLMh4uYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuVmFsdWUSDgoGY3Vyc29yGAMgASgMIvcCChBRdWVyeVJlc3VsdEJhdGNoEkwKEmVudGl0eV9yZXN1bHRfdHlwZRgBIAIoDjIwLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkVudGl0eVJlc3VsdC5SZXN1bHRUeXBlEjwKDWVudGl0eV9yZXN1bHQYAiADKAsyJS5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5FbnRpdHlSZXN1bHQSEgoKZW5kX2N1cnNvchgEIAEoDBJPCgxtb3JlX3Jlc3VsdHMYBSACKA4yOS5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5RdWVyeVJlc3VsdEJhdGNoLk1vcmVSZXN1bHRzVHlwZRIaCg9za2lwcGVkX3Jlc3VsdHMYBiABKAU6ATAiVgoPTW9yZVJlc3VsdHNUeXBlEhAKDE5PVF9GSU5JU0hFRBABEhwKGE1PUkVfUkVTVUxUU19BRlRFUl9MSU1JVBACEhMKD05PX01PUkVfUkVTVUxUUxADIvUBCghNdXRhdGlvbhI3CgJvcBgBIAIoDjIrLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0Lk11dGF0aW9uLk9wZXJhdGlvbhIpCgNrZXkYAiABKAsyHC5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5LZXkSLwoGZW50aXR5GAMgASgLMh8uYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuRW50aXR5IlQKCU9wZXJhdGlvbhIKCgZJTlNFUlQQARIKCgZVUERBVEUQAhIKCgZVUFNFUlQQAxIKCgZERUxFVEUQBBIXChNJTlNFUlRfV0lUSF9BVVRPX0lEEGMiUwoOTXV0YXRpb25SZXN1bHQSKQoDa2V5GAMgASgLMhwuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuS2V5EhYKC25ld192ZXJzaW9uGAQgASgDOgEwIqQCChJEZXByZWNhdGVkTXV0YXRpb24SLwoGdXBzZXJ0GAEgAygLMh8uYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuRW50aXR5Ei8KBnVwZGF0ZRgCIAMoCzIfLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkVudGl0eRIvCgZpbnNlcnQYAyADKAsyHy5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5FbnRpdHkSNwoOaW5zZXJ0X2F1dG9faWQYBCADKAsyHy5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5FbnRpdHkSLAoGZGVsZXRlGAUgAygLMhwuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuS2V5EhQKBWZvcmNlGAYgASgIOgVmYWxzZSLrAQoYRGVwcmVjYXRlZE11dGF0aW9uUmVzdWx0EhUKDWluZGV4X3VwZGF0ZXMYASACKAUSOAoSaW5zZXJ0X2F1dG9faWRfa2V5GAIgAygLMhwuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuS2V5EhYKDnVwc2VydF92ZXJzaW9uGAMgAygDEhYKDnVwZGF0ZV92ZXJzaW9uGAQgAygDEhYKDmluc2VydF92ZXJzaW9uGAUgAygDEh4KFmluc2VydF9hdXRvX2lkX3ZlcnNpb24YBiADKAMSFgoOZGVsZXRlX3ZlcnNpb24YByADKAMitQEKC1JlYWRPcHRpb25zElcKEHJlYWRfY29uc2lzdGVuY3kYASABKA4yNC5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5SZWFkT3B0aW9ucy5SZWFkQ29uc2lzdGVuY3k6B0RFRkFVTFQSEwoLdHJhbnNhY3Rpb24YAiABKAwiOAoPUmVhZENvbnNpc3RlbmN5EgsKB0RFRkFVTFQQABIKCgZTVFJPTkcQARIMCghFVkVOVFVBTBACInYKDUxvb2t1cFJlcXVlc3QSOgoMcmVhZF9vcHRpb25zGAEgASgLMiQuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuUmVhZE9wdGlvbnMSKQoDa2V5GAMgAygLMhwuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuS2V5Iq4BCg5Mb29rdXBSZXNwb25zZRI0CgVmb3VuZBgBIAMoCzIlLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkVudGl0eVJlc3VsdBI2CgdtaXNzaW5nGAIgAygLMiUuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuRW50aXR5UmVzdWx0Ei4KCGRlZmVycmVkGAMgAygLMhwuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuS2V5IqsCCg9SdW5RdWVyeVJlcXVlc3QSOgoMcmVhZF9vcHRpb25zGAEgASgLMiQuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuUmVhZE9wdGlvbnMSOgoMcGFydGl0aW9uX2lkGAIgASgLMiQuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuUGFydGl0aW9uSWQSLQoFcXVlcnkYAyABKAsyHi5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5RdWVyeRI0CglncWxfcXVlcnkYByABKAsyIS5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5HcWxRdWVyeRIdChVtaW5fc2FmZV90aW1lX3NlY29uZHMYBCABKAMSHAoUc3VnZ2VzdGVkX2JhdGNoX3NpemUYBSABKAUiYgoQUnVuUXVlcnlSZXNwb25zZRI4CgViYXRjaBgBIAIoCzIpLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LlF1ZXJ5UmVzdWx0QmF0Y2gSFAoMcXVlcnlfaGFuZGxlGAIgASgMIiwKFENvbnRpbnVlUXVlcnlSZXF1ZXN0EhQKDHF1ZXJ5X2hhbmRsZRgBIAIoDCJRChVDb250aW51ZVF1ZXJ5UmVzcG9uc2USOAoFYmF0Y2gYASACKAsyKS5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5RdWVyeVJlc3VsdEJhdGNoIlMKF0JlZ2luVHJhbnNhY3Rpb25SZXF1ZXN0EhoKC2Nyb3NzX2dyb3VwGAEgASgIOgVmYWxzZRIcCg1jcm9zc19yZXF1ZXN0GAIgASgIOgVmYWxzZSIvChhCZWdpblRyYW5zYWN0aW9uUmVzcG9uc2USEwoLdHJhbnNhY3Rpb24YASACKAwiJgoPUm9sbGJhY2tSZXF1ZXN0EhMKC3RyYW5zYWN0aW9uGAEgAigMIhIKEFJvbGxiYWNrUmVzcG9uc2UiwAIKDUNvbW1pdFJlcXVlc3QSEwoLdHJhbnNhY3Rpb24YASABKAwSMwoIbXV0YXRpb24YBSADKAsyIS5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5NdXRhdGlvbhJIChNkZXByZWNhdGVkX211dGF0aW9uGAIgASgLMisuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuRGVwcmVjYXRlZE11dGF0aW9uEkgKBG1vZGUYBCABKA4yKy5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5Db21taXRSZXF1ZXN0Lk1vZGU6DVRSQU5TQUNUSU9OQUwSHwoQaWdub3JlX3JlYWRfb25seRgGIAEoCDoFZmFsc2UiMAoETW9kZRIRCg1UUkFOU0FDVElPTkFMEAESFQoRTk9OX1RSQU5TQUNUSU9OQUwQAiLAAQoOQ29tbWl0UmVzcG9uc2USQAoPbXV0YXRpb25fcmVzdWx0GAMgAygLMicuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuTXV0YXRpb25SZXN1bHQSVQoaZGVwcmVjYXRlZF9tdXRhdGlvbl9yZXN1bHQYASABKAsyMS5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5EZXByZWNhdGVkTXV0YXRpb25SZXN1bHQSFQoNaW5kZXhfdXBkYXRlcxgEIAEoBSJzChJBbGxvY2F0ZUlkc1JlcXVlc3QSLgoIYWxsb2NhdGUYASADKAsyHC5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5LZXkSLQoHcmVzZXJ2ZRgCIAMoCzIcLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LktleSJGChNBbGxvY2F0ZUlkc1Jlc3BvbnNlEi8KCWFsbG9jYXRlZBgBIAMoCzIcLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LktleSJYCgxXcml0ZVJlcXVlc3QSSAoTZGVwcmVjYXRlZF9tdXRhdGlvbhgBIAIoCzIrLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkRlcHJlY2F0ZWRNdXRhdGlvbjKiBwoSRGF0YXN0b3JlVjRTZXJ2aWNlEnkKEEJlZ2luVHJhbnNhY3Rpb24SMC5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5CZWdpblRyYW5zYWN0aW9uUmVxdWVzdBoxLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkJlZ2luVHJhbnNhY3Rpb25SZXNwb25zZSIAEmEKCFJvbGxiYWNrEiguYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuUm9sbGJhY2tSZXF1ZXN0GikuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuUm9sbGJhY2tSZXNwb25zZSIAElsKBkNvbW1pdBImLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkNvbW1pdFJlcXVlc3QaJy5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5Db21taXRSZXNwb25zZSIAEmEKCFJ1blF1ZXJ5EiguYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuUnVuUXVlcnlSZXF1ZXN0GikuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuUnVuUXVlcnlSZXNwb25zZSIAEnAKDUNvbnRpbnVlUXVlcnkSLS5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5Db250aW51ZVF1ZXJ5UmVxdWVzdBouLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkNvbnRpbnVlUXVlcnlSZXNwb25zZSIAElsKBkxvb2t1cBImLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0Lkxvb2t1cFJlcXVlc3QaJy5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5Mb29rdXBSZXNwb25zZSIAEmoKC0FsbG9jYXRlSWRzEisuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuQWxsb2NhdGVJZHNSZXF1ZXN0GiwuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuQWxsb2NhdGVJZHNSZXNwb25zZSIAElgKA0dldBImLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0Lkxvb2t1cFJlcXVlc3QaJy5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5Mb29rdXBSZXNwb25zZSIAElkKBVdyaXRlEiUuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuV3JpdGVSZXF1ZXN0GicuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuQ29tbWl0UmVzcG9uc2UiAEIhCh9jb20uZ29vZ2xlLmFwcGhvc3RpbmcuZGF0YXN0b3Jl"))
+  _SERIALIZED_DESCRIPTOR.fromstring(base64.decodestring("WidhcHBob3N0aW5nL2RhdGFzdG9yZS9kYXRhc3RvcmVfdjQucHJvdG8KHWFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkVycm9yc3oJRXJyb3JDb2RliwGSAQtCQURfUkVRVUVTVJgBAYwBiwGSARZDT05DVVJSRU5UX1RSQU5TQUNUSU9OmAECjAGLAZIBDklOVEVSTkFMX0VSUk9SmAEDjAGLAZIBCk5FRURfSU5ERViYAQSMAYsBkgEHVElNRU9VVJgBBYwBiwGSARFQRVJNSVNTSU9OX0RFTklFRJgBBowBiwGSAQ5CSUdUQUJMRV9FUlJPUpgBB4wBiwGSARxDT01NSVRURURfQlVUX1NUSUxMX0FQUExZSU5HmAEIjAGLAZIBE0NBUEFCSUxJVFlfRElTQUJMRUSYAQmMAYsBkgEVVFJZX0FMVEVSTkFURV9CQUNLRU5EmAEKjAGLAZIBEVNBRkVfVElNRV9UT09fT0xEmAELjAF0ugG2MgonYXBwaG9zdGluZy9kYXRhc3RvcmUvZGF0YXN0b3JlX3Y0LnByb3RvEhdhcHBob3N0aW5nLmRhdGFzdG9yZS52NBokYXBwaG9zdGluZy9kYXRhc3RvcmUvZW50aXR5X3Y0LnByb3RvIosCCgVFcnJvciKBAgoJRXJyb3JDb2RlEg8KC0JBRF9SRVFVRVNUEAESGgoWQ09OQ1VSUkVOVF9UUkFOU0FDVElPThACEhIKDklOVEVSTkFMX0VSUk9SEAMSDgoKTkVFRF9JTkRFWBAEEgsKB1RJTUVPVVQQBRIVChFQRVJNSVNTSU9OX0RFTklFRBAGEhIKDkJJR1RBQkxFX0VSUk9SEAcSIAocQ09NTUlUVEVEX0JVVF9TVElMTF9BUFBMWUlORxAIEhcKE0NBUEFCSUxJVFlfRElTQUJMRUQQCRIZChVUUllfQUxURVJOQVRFX0JBQ0tFTkQQChIVChFTQUZFX1RJTUVfVE9PX09MRBALIoYBCgxFbnRpdHlSZXN1bHQSLwoGZW50aXR5GAEgAigLMh8uYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuRW50aXR5Eg8KB3ZlcnNpb24YAiABKAMiNAoKUmVzdWx0VHlwZRIICgRGVUxMEAESDgoKUFJPSkVDVElPThACEgwKCEtFWV9PTkxZEAMi8QIKBVF1ZXJ5Ej8KCnByb2plY3Rpb24YAiADKAsyKy5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5Qcm9wZXJ0eUV4cHJlc3Npb24SNQoEa2luZBgDIAMoCzInLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LktpbmRFeHByZXNzaW9uEi8KBmZpbHRlchgEIAEoCzIfLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkZpbHRlchI1CgVvcmRlchgFIAMoCzImLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LlByb3BlcnR5T3JkZXISPAoIZ3JvdXBfYnkYBiADKAsyKi5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5Qcm9wZXJ0eVJlZmVyZW5jZRIUCgxzdGFydF9jdXJzb3IYByABKAwSEgoKZW5kX2N1cnNvchgIIAEoDBIRCgZvZmZzZXQYCiABKAU6ATASDQoFbGltaXQYCyABKAUiHgoOS2luZEV4cHJlc3Npb24SDAoEbmFtZRgBIAIoCSIhChFQcm9wZXJ0eVJlZmVyZW5jZRIMCgRuYW1lGAIgAigJItMBChJQcm9wZXJ0eUV4cHJlc3Npb24SPAoIcHJvcGVydHkYASACKAsyKi5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5Qcm9wZXJ0eVJlZmVyZW5jZRJdChRhZ2dyZWdhdGlvbl9mdW5jdGlvbhgCIAEoDjI/LmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LlByb3BlcnR5RXhwcmVzc2lvbi5BZ2dyZWdhdGlvbkZ1bmN0aW9uIiAKE0FnZ3JlZ2F0aW9uRnVuY3Rpb24SCQoFRklSU1QQASLJAQoNUHJvcGVydHlPcmRlchI8Cghwcm9wZXJ0eRgBIAIoCzIqLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LlByb3BlcnR5UmVmZXJlbmNlEk4KCWRpcmVjdGlvbhgCIAEoDjIwLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LlByb3BlcnR5T3JkZXIuRGlyZWN0aW9uOglBU0NFTkRJTkciKgoJRGlyZWN0aW9uEg0KCUFTQ0VORElORxABEg4KCkRFU0NFTkRJTkcQAiKOAQoGRmlsdGVyEkIKEGNvbXBvc2l0ZV9maWx0ZXIYASABKAsyKC5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5Db21wb3NpdGVGaWx0ZXISQAoPcHJvcGVydHlfZmlsdGVyGAIgASgLMicuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuUHJvcGVydHlGaWx0ZXIinAEKD0NvbXBvc2l0ZUZpbHRlchJDCghvcGVyYXRvchgBIAIoDjIxLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkNvbXBvc2l0ZUZpbHRlci5PcGVyYXRvchIvCgZmaWx0ZXIYAiADKAsyHy5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5GaWx0ZXIiEwoIT3BlcmF0b3ISBwoDQU5EEAEivgIKDlByb3BlcnR5RmlsdGVyEjwKCHByb3BlcnR5GAEgAigLMiouYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuUHJvcGVydHlSZWZlcmVuY2USQgoIb3BlcmF0b3IYAiACKA4yMC5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5Qcm9wZXJ0eUZpbHRlci5PcGVyYXRvchItCgV2YWx1ZRgDIAIoCzIeLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LlZhbHVlInsKCE9wZXJhdG9yEg0KCUxFU1NfVEhBThABEhYKEkxFU1NfVEhBTl9PUl9FUVVBTBACEhAKDEdSRUFURVJfVEhBThADEhkKFUdSRUFURVJfVEhBTl9PUl9FUVVBTBAEEgkKBUVRVUFMEAUSEAoMSEFTX0FOQ0VTVE9SEAsisAEKCEdxbFF1ZXJ5EhQKDHF1ZXJ5X3N0cmluZxgBIAIoCRIcCg1hbGxvd19saXRlcmFsGAIgASgIOgVmYWxzZRI2CghuYW1lX2FyZxgDIAMoCzIkLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkdxbFF1ZXJ5QXJnEjgKCm51bWJlcl9hcmcYBCADKAsyJC5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5HcWxRdWVyeUFyZyJaCgtHcWxRdWVyeUFyZxIMCgRuYW1lGAEgASgJEi0KBXZhbHVlGAIgASgLMh4uYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuVmFsdWUSDgoGY3Vyc29yGAMgASgMIvcCChBRdWVyeVJlc3VsdEJhdGNoEkwKEmVudGl0eV9yZXN1bHRfdHlwZRgBIAIoDjIwLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkVudGl0eVJlc3VsdC5SZXN1bHRUeXBlEjwKDWVudGl0eV9yZXN1bHQYAiADKAsyJS5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5FbnRpdHlSZXN1bHQSEgoKZW5kX2N1cnNvchgEIAEoDBJPCgxtb3JlX3Jlc3VsdHMYBSACKA4yOS5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5RdWVyeVJlc3VsdEJhdGNoLk1vcmVSZXN1bHRzVHlwZRIaCg9za2lwcGVkX3Jlc3VsdHMYBiABKAU6ATAiVgoPTW9yZVJlc3VsdHNUeXBlEhAKDE5PVF9GSU5JU0hFRBABEhwKGE1PUkVfUkVTVUxUU19BRlRFUl9MSU1JVBACEhMKD05PX01PUkVfUkVTVUxUUxADItwBCghNdXRhdGlvbhI3CgJvcBgBIAIoDjIrLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0Lk11dGF0aW9uLk9wZXJhdGlvbhIpCgNrZXkYAiABKAsyHC5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5LZXkSLwoGZW50aXR5GAMgASgLMh8uYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuRW50aXR5IjsKCU9wZXJhdGlvbhIKCgZJTlNFUlQQARIKCgZVUERBVEUQAhIKCgZVUFNFUlQQAxIKCgZERUxFVEUQBCJTCg5NdXRhdGlvblJlc3VsdBIpCgNrZXkYAyABKAsyHC5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5LZXkSFgoLbmV3X3ZlcnNpb24YBCABKAM6ATAipAIKEkRlcHJlY2F0ZWRNdXRhdGlvbhIvCgZ1cHNlcnQYASADKAsyHy5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5FbnRpdHkSLwoGdXBkYXRlGAIgAygLMh8uYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuRW50aXR5Ei8KBmluc2VydBgDIAMoCzIfLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkVudGl0eRI3Cg5pbnNlcnRfYXV0b19pZBgEIAMoCzIfLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkVudGl0eRIsCgZkZWxldGUYBSADKAsyHC5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5LZXkSFAoFZm9yY2UYBiABKAg6BWZhbHNlIusBChhEZXByZWNhdGVkTXV0YXRpb25SZXN1bHQSFQoNaW5kZXhfdXBkYXRlcxgBIAIoBRI4ChJpbnNlcnRfYXV0b19pZF9rZXkYAiADKAsyHC5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5LZXkSFgoOdXBzZXJ0X3ZlcnNpb24YAyADKAMSFgoOdXBkYXRlX3ZlcnNpb24YBCADKAMSFgoOaW5zZXJ0X3ZlcnNpb24YBSADKAMSHgoWaW5zZXJ0X2F1dG9faWRfdmVyc2lvbhgGIAMoAxIWCg5kZWxldGVfdmVyc2lvbhgHIAMoAyK1AQoLUmVhZE9wdGlvbnMSVwoQcmVhZF9jb25zaXN0ZW5jeRgBIAEoDjI0LmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LlJlYWRPcHRpb25zLlJlYWRDb25zaXN0ZW5jeToHREVGQVVMVBITCgt0cmFuc2FjdGlvbhgCIAEoDCI4Cg9SZWFkQ29uc2lzdGVuY3kSCwoHREVGQVVMVBAAEgoKBlNUUk9ORxABEgwKCEVWRU5UVUFMEAIidgoNTG9va3VwUmVxdWVzdBI6CgxyZWFkX29wdGlvbnMYASABKAsyJC5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5SZWFkT3B0aW9ucxIpCgNrZXkYAyADKAsyHC5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5LZXkirgEKDkxvb2t1cFJlc3BvbnNlEjQKBWZvdW5kGAEgAygLMiUuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuRW50aXR5UmVzdWx0EjYKB21pc3NpbmcYAiADKAsyJS5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5FbnRpdHlSZXN1bHQSLgoIZGVmZXJyZWQYAyADKAsyHC5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5LZXkiqwIKD1J1blF1ZXJ5UmVxdWVzdBI6CgxyZWFkX29wdGlvbnMYASABKAsyJC5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5SZWFkT3B0aW9ucxI6CgxwYXJ0aXRpb25faWQYAiABKAsyJC5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5QYXJ0aXRpb25JZBItCgVxdWVyeRgDIAEoCzIeLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LlF1ZXJ5EjQKCWdxbF9xdWVyeRgHIAEoCzIhLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkdxbFF1ZXJ5Eh0KFW1pbl9zYWZlX3RpbWVfc2Vjb25kcxgEIAEoAxIcChRzdWdnZXN0ZWRfYmF0Y2hfc2l6ZRgFIAEoBSJiChBSdW5RdWVyeVJlc3BvbnNlEjgKBWJhdGNoGAEgAigLMikuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuUXVlcnlSZXN1bHRCYXRjaBIUCgxxdWVyeV9oYW5kbGUYAiABKAwiLAoUQ29udGludWVRdWVyeVJlcXVlc3QSFAoMcXVlcnlfaGFuZGxlGAEgAigMIlEKFUNvbnRpbnVlUXVlcnlSZXNwb25zZRI4CgViYXRjaBgBIAIoCzIpLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LlF1ZXJ5UmVzdWx0QmF0Y2giUwoXQmVnaW5UcmFuc2FjdGlvblJlcXVlc3QSGgoLY3Jvc3NfZ3JvdXAYASABKAg6BWZhbHNlEhwKDWNyb3NzX3JlcXVlc3QYAiABKAg6BWZhbHNlIi8KGEJlZ2luVHJhbnNhY3Rpb25SZXNwb25zZRITCgt0cmFuc2FjdGlvbhgBIAIoDCImCg9Sb2xsYmFja1JlcXVlc3QSEwoLdHJhbnNhY3Rpb24YASACKAwiEgoQUm9sbGJhY2tSZXNwb25zZSLAAgoNQ29tbWl0UmVxdWVzdBITCgt0cmFuc2FjdGlvbhgBIAEoDBIzCghtdXRhdGlvbhgFIAMoCzIhLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0Lk11dGF0aW9uEkgKE2RlcHJlY2F0ZWRfbXV0YXRpb24YAiABKAsyKy5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5EZXByZWNhdGVkTXV0YXRpb24SSAoEbW9kZRgEIAEoDjIrLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkNvbW1pdFJlcXVlc3QuTW9kZToNVFJBTlNBQ1RJT05BTBIfChBpZ25vcmVfcmVhZF9vbmx5GAYgASgIOgVmYWxzZSIwCgRNb2RlEhEKDVRSQU5TQUNUSU9OQUwQARIVChFOT05fVFJBTlNBQ1RJT05BTBACIsABCg5Db21taXRSZXNwb25zZRJACg9tdXRhdGlvbl9yZXN1bHQYAyADKAsyJy5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5NdXRhdGlvblJlc3VsdBJVChpkZXByZWNhdGVkX211dGF0aW9uX3Jlc3VsdBgBIAEoCzIxLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkRlcHJlY2F0ZWRNdXRhdGlvblJlc3VsdBIVCg1pbmRleF91cGRhdGVzGAQgASgFInMKEkFsbG9jYXRlSWRzUmVxdWVzdBIuCghhbGxvY2F0ZRgBIAMoCzIcLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LktleRItCgdyZXNlcnZlGAIgAygLMhwuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuS2V5IkYKE0FsbG9jYXRlSWRzUmVzcG9uc2USLwoJYWxsb2NhdGVkGAEgAygLMhwuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuS2V5IlgKDFdyaXRlUmVxdWVzdBJIChNkZXByZWNhdGVkX211dGF0aW9uGAEgAigLMisuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuRGVwcmVjYXRlZE11dGF0aW9uMqIHChJEYXRhc3RvcmVWNFNlcnZpY2USeQoQQmVnaW5UcmFuc2FjdGlvbhIwLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkJlZ2luVHJhbnNhY3Rpb25SZXF1ZXN0GjEuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuQmVnaW5UcmFuc2FjdGlvblJlc3BvbnNlIgASYQoIUm9sbGJhY2sSKC5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5Sb2xsYmFja1JlcXVlc3QaKS5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5Sb2xsYmFja1Jlc3BvbnNlIgASWwoGQ29tbWl0EiYuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuQ29tbWl0UmVxdWVzdBonLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkNvbW1pdFJlc3BvbnNlIgASYQoIUnVuUXVlcnkSKC5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5SdW5RdWVyeVJlcXVlc3QaKS5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5SdW5RdWVyeVJlc3BvbnNlIgAScAoNQ29udGludWVRdWVyeRItLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkNvbnRpbnVlUXVlcnlSZXF1ZXN0Gi4uYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuQ29udGludWVRdWVyeVJlc3BvbnNlIgASWwoGTG9va3VwEiYuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuTG9va3VwUmVxdWVzdBonLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0Lkxvb2t1cFJlc3BvbnNlIgASagoLQWxsb2NhdGVJZHMSKy5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5BbGxvY2F0ZUlkc1JlcXVlc3QaLC5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5BbGxvY2F0ZUlkc1Jlc3BvbnNlIgASWAoDR2V0EiYuYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuTG9va3VwUmVxdWVzdBonLmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0Lkxvb2t1cFJlc3BvbnNlIgASWQoFV3JpdGUSJS5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5Xcml0ZVJlcXVlc3QaJy5hcHBob3N0aW5nLmRhdGFzdG9yZS52NC5Db21taXRSZXNwb25zZSIAQiEKH2NvbS5nb29nbGUuYXBwaG9zdGluZy5kYXRhc3RvcmU="))
   if _net_proto___parse__python is not None:
     _net_proto___parse__python.RegisterType(
         _SERIALIZED_DESCRIPTOR.tostring())
@@ -2922,14 +2922,12 @@
   UPDATE       =    2
   UPSERT       =    3
   DELETE       =    4
-  INSERT_WITH_AUTO_ID =   99
 
   _Operation_NAMES = {
     1: "INSERT",
     2: "UPDATE",
     3: "UPSERT",
     4: "DELETE",
-    99: "INSERT_WITH_AUTO_ID",
   }
 
   def Operation_Name(cls, x): return cls._Operation_NAMES.get(x, "")
@@ -3161,7 +3159,7 @@
   _STYLE_CONTENT_TYPE = """"""
   _PROTO_DESCRIPTOR_NAME = 'apphosting.datastore.v4.Mutation'
   _SERIALIZED_DESCRIPTOR = array.array('B')
-  _SERIALIZED_DESCRIPTOR.fromstring(base64.decodestring("WidhcHBob3N0aW5nL2RhdGFzdG9yZS9kYXRhc3RvcmVfdjQucHJvdG8KIGFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0Lk11dGF0aW9uExoCb3AgASgAMAU4AmgAFBMaA2tleSACKAIwCzgBShthcHBob3N0aW5nLmRhdGFzdG9yZS52NC5LZXmjAaoBBWN0eXBlsgEGcHJvdG8ypAEUExoGZW50aXR5IAMoAjALOAFKHmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkVudGl0eaMBqgEFY3R5cGWyAQZwcm90bzKkARRzeglPcGVyYXRpb26LAZIBBklOU0VSVJgBAYwBiwGSAQZVUERBVEWYAQKMAYsBkgEGVVBTRVJUmAEDjAGLAZIBBkRFTEVURZgBBIwBiwGSARNJTlNFUlRfV0lUSF9BVVRPX0lEmAFjjAF0wgEdYXBwaG9zdGluZy5kYXRhc3RvcmUudjQuRXJyb3I="))
+  _SERIALIZED_DESCRIPTOR.fromstring(base64.decodestring("WidhcHBob3N0aW5nL2RhdGFzdG9yZS9kYXRhc3RvcmVfdjQucHJvdG8KIGFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0Lk11dGF0aW9uExoCb3AgASgAMAU4AmgAFBMaA2tleSACKAIwCzgBShthcHBob3N0aW5nLmRhdGFzdG9yZS52NC5LZXmjAaoBBWN0eXBlsgEGcHJvdG8ypAEUExoGZW50aXR5IAMoAjALOAFKHmFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkVudGl0eaMBqgEFY3R5cGWyAQZwcm90bzKkARRzeglPcGVyYXRpb26LAZIBBklOU0VSVJgBAYwBiwGSAQZVUERBVEWYAQKMAYsBkgEGVVBTRVJUmAEDjAGLAZIBBkRFTEVURZgBBIwBdMIBHWFwcGhvc3RpbmcuZGF0YXN0b3JlLnY0LkVycm9y"))
   if _net_proto___parse__python is not None:
     _net_proto___parse__python.RegisterType(
         _SERIALIZED_DESCRIPTOR.tostring())
diff --git a/google/appengine/datastore/document_pb.py b/google/appengine/datastore/document_pb.py
index a5756d3..25773a6 100644
--- a/google/appengine/datastore/document_pb.py
+++ b/google/appengine/datastore/document_pb.py
@@ -673,12 +673,10 @@
 
 
   ATOM         =    2
-  DATE         =    3
   NUMBER       =    4
 
   _ContentType_NAMES = {
     2: "ATOM",
-    3: "DATE",
     4: "NUMBER",
   }
 
diff --git a/google/appengine/ext/admin/__init__.py b/google/appengine/ext/admin/__init__.py
index 2282913..eba729b 100644
--- a/google/appengine/ext/admin/__init__.py
+++ b/google/appengine/ext/admin/__init__.py
@@ -325,7 +325,14 @@
   PATH = '/interactive'
 
   def get(self):
-    self.generate('interactive.html')
+
+    if users.is_current_user_admin():
+      self.generate('interactive.html')
+    else:
+      logging.warning(
+          'Non admin user from IP %s attempted to use interactive console',
+          self.request.remote_addr)
+      self.error(404)
 
 
 class InteractiveExecuteHandler(BaseRequestHandler):
@@ -339,35 +346,41 @@
 
   @xsrf_required
   def post(self):
-    if self.interactive_console_enabled():
+    if users.is_current_user_admin():
+      if self.interactive_console_enabled():
 
-      save_stdout = sys.stdout
-      results_io = cStringIO.StringIO()
-      try:
-        sys.stdout = results_io
-
-
-        code = self.request.get('code')
-        code = code.replace("\r\n", "\n")
-
+        save_stdout = sys.stdout
+        results_io = cStringIO.StringIO()
         try:
-          compiled_code = compile(code, '<string>', 'exec')
-          exec(compiled_code, globals())
-        except Exception, e:
-          traceback.print_exc(file=results_io)
-      finally:
-        sys.stdout = save_stdout
+          sys.stdout = results_io
 
-      results = results_io.getvalue()
-    else:
-      results = """The interactive console has been disabled for security
+
+          code = self.request.get('code')
+          code = code.replace('\r\n', '\n')
+
+          try:
+            compiled_code = compile(code, '<string>', 'exec')
+            exec(compiled_code, globals())
+          except Exception, e:
+            traceback.print_exc(file=results_io)
+        finally:
+          sys.stdout = save_stdout
+
+        results = results_io.getvalue()
+      else:
+        results = """The interactive console has been disabled for security
 because the dev_appserver is listening on a non-default address.
 If you would like to re-enable the console, invoke dev_appserver
 with the --enable_console argument.
 
 See https://developers.google.com/appengine/docs/python/tools/devserver#The_Interactive_Console
 for more information."""
-    self.generate('interactive-output.html', {'output': results})
+      self.generate('interactive-output.html', {'output': results})
+    else:
+      logging.warning(
+          'Non admin user from IP %s attempted to use interactive console',
+          self.request.remote_addr)
+      self.error(404)
 
 
 class CronPageHandler(BaseRequestHandler):
diff --git a/google/appengine/ext/analytics/static/analytics_js.js b/google/appengine/ext/analytics/static/analytics_js.js
index fc4c21a..9bb2c41 100644
--- a/google/appengine/ext/analytics/static/analytics_js.js
+++ b/google/appengine/ext/analytics/static/analytics_js.js
@@ -1,25 +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=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},s=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},t=function(a,b){function c(){}
-c.prototype=b.prototype;a.n=b.prototype;a.prototype=new c;a.q=function(a,c,f){var g=Array.prototype.slice.call(arguments,2);return b.prototype[c].apply(a,g)}};var u=function(a){Error.captureStackTrace?Error.captureStackTrace(this,u):this.stack=Error().stack||"";a&&(this.message=String(a))};t(u,Error);var ba=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")},w=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},x=function(a,b){return a<b?-1:a>b?1:0};var y=function(a,b){b.unshift(a);u.call(this,ba.apply(null,b));b.shift()};t(y,u);var z=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 y(""+d,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 e=a.length,d=q(a)?a.split(""):a,f=0;f<e;f++)f in d&&b.call(c,d[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 e=a.length,d=[],f=0,g=q(a)?a.split(""):a,h=0;h<e;h++)if(h in g){var v=g[h];b.call(c,v,h,a)&&(d[f++]=v)}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[]},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 E,F,G,H,fa=function(){return m.navigator?m.navigator.userAgent:null};H=G=F=E=!1;var I;if(I=fa()){var ga=m.navigator;E=0==I.lastIndexOf("Opera",0);F=!E&&(-1!=I.indexOf("MSIE")||-1!=I.indexOf("Trident"));G=!E&&-1!=I.indexOf("WebKit");H=!E&&!G&&!F&&"Gecko"==ga.product}var ha=E,J=F,K=H,L=G,ia=function(){var a=m.document;return a?a.documentMode:void 0},M;
+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},s=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},t=function(a,b){function c(){}
+c.prototype=b.prototype;a.o=b.prototype;a.prototype=new c;a.r=function(a,c,f){return b.prototype[c].apply(a,Array.prototype.slice.call(arguments,2))}};var u=function(a){if(Error.captureStackTrace)Error.captureStackTrace(this,u);else{var b=Error().stack;b&&(this.stack=b)}a&&(this.message=String(a))};t(u,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")},w=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},x=function(a,b){return a<b?-1:a>b?1:0};var y=function(a,b){b.unshift(a);u.call(this,ba.apply(null,b));b.shift()};t(y,u);var z=function(a,b,c){if(!a){var d="Assertion failed";if(b)var d=d+(": "+b),e=Array.prototype.slice.call(arguments,2);throw new y(""+d,e||[]);}};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,h=0;h<d;h++)if(h in g){var v=g[h];b.call(c,v,h,a)&&(e[f++]=v)}return e},D=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 E,F,G,H,fa=function(){return m.navigator?m.navigator.userAgent:null};H=G=F=E=!1;var I;if(I=fa()){var ga=m.navigator;E=0==I.lastIndexOf("Opera",0);F=!E&&(-1!=I.indexOf("MSIE")||-1!=I.indexOf("Trident"));G=!E&&-1!=I.indexOf("WebKit");H=!E&&!G&&!F&&"Gecko"==ga.product}var ha=E,J=F,K=H,L=G,ia=function(){var a=m.document;return a?a.documentMode:void 0},M;
 t:{var N="",O;if(ha&&m.opera)var P=m.opera.version,N="function"==typeof P?P():P;else if(K?O=/rv\:([^\);]+)(\)|;)/:J?O=/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/:L&&(O=/WebKit\/(\S+)/),O)var ja=O.exec(fa()),N=ja?ja[1]:"";if(J){var ka=ia();if(ka>parseFloat(N)){M=String(ka);break t}}M=N}
-var la=M,ma={},Q=function(a){var b;if(!(b=ma[a])){b=0;for(var c=String(la).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]||"",v=RegExp("(\\d*)(\\D*)","g"),p=RegExp("(\\d*)(\\D*)","g");do{var l=v.exec(g)||["","",""],C=p.exec(h)||["","",""];if(0==l[0].length&&0==C[0].length)break;b=x(0==l[1].length?0:parseInt(l[1],10),0==C[1].length?0:parseInt(C[1],10))||x(0==l[2].length,
-0==C[2].length)||x(l[2],C[2])}while(0==b)}b=ma[a]=0<=b}return b},na=m.document,oa=na&&J?ia()||("CSS1Compat"==na.compatMode?parseInt(la,10):5):void 0;!K&&!J||J&&J&&9<=oa||K&&Q("1.9.1");J&&Q("9");var pa=function(a){a=a.className;return q(a)&&a.match(/\S+/g)||[]},qa=function(a,b){for(var c=pa(a),e=ea(arguments,1),d=c,f=0;f<e.length;f++)0<=B(d,e[f])||d.push(e[f]);c=c.join(" ");a.className=c},sa=function(a,b){var c=pa(a),e=ea(arguments,1),c=ra(c,e).join(" ");a.className=c},ra=function(a,b){return da(a,function(a){return!(0<=B(b,a))})};var R=function(a,b,c){var e=document;c=c||e;var d=a&&"*"!=a?a.toUpperCase():"";if(c.querySelectorAll&&c.querySelector&&(d||b))return c.querySelectorAll(d+(b?"."+b:""));if(b&&c.getElementsByClassName){a=c.getElementsByClassName(b);if(d){c={};for(var f=e=0,g;g=a[f];f++)d==g.nodeName&&(c[e++]=g);c.length=e;return c}return a}a=c.getElementsByTagName(d||"*");if(b){c={};for(f=e=0;g=a[f];f++){var d=g.className,h;if(h="function"==typeof d.split)h=0<=B(d.split(/\s+/),b);h&&(c[e++]=g)}c.length=e;return c}return a};var S=function(a){S[" "](a);return a};S[" "]=function(){};var ta=!J||J&&9<=oa,ua=J&&!Q("9");!L||Q("528");K&&Q("1.9b")||J&&Q("8")||ha&&Q("9.5")||L&&Q("528");K&&!Q("8")||J&&Q("9");var T=function(a,b){this.type=a;this.currentTarget=this.target=b;this.defaultPrevented=this.i=!1};T.prototype.preventDefault=function(){this.defaultPrevented=!0};var U=function(a,b){T.call(this,a?a.type:"");this.relatedTarget=this.currentTarget=this.target=null;this.charCode=this.keyCode=this.button=this.screenY=this.screenX=this.clientY=this.clientX=this.offsetY=this.offsetX=0;this.metaKey=this.shiftKey=this.altKey=this.ctrlKey=!1;this.j=this.state=null;if(a){var c=this.type=a.type;this.target=a.target||a.srcElement;this.currentTarget=b;var e=a.relatedTarget;if(e){if(K){var d;t:{try{S(e.nodeName);d=!0;break t}catch(f){}d=!1}d||(e=null)}}else"mouseover"==
-c?e=a.fromElement:"mouseout"==c&&(e=a.toElement);this.relatedTarget=e;this.offsetX=L||void 0!==a.offsetX?a.offsetX:a.layerX;this.offsetY=L||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()}};t(U,T);U.prototype.preventDefault=function(){U.n.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,e,d){this.c=a;this.e=null;this.src=b;this.type=c;this.capture=!!e;this.f=d;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 V=function(a){this.src=a;this.b={};this.h=0};V.prototype.add=function(a,b,c,e,d){var f=this.b[a];f||(f=this.b[a]=[],this.h++);var g;t:{for(g=0;g<f.length;++g){var h=f[g];if(!h.d&&h.c==b&&h.capture==!!e&&h.f==d)break t}g=-1}-1<g?(a=f[g],c||(a.g=!1)):(a=new ya(b,this.src,a,!!e,d),a.g=c,f.push(a));return a};var Aa=function(a,b){var c=b.type;if(c in a.b){var e=a.b[c],d=B(e,b),f;if(f=0<=d)z(null!=e.length),A.splice.call(e,d,1);f&&(za(b),0==a.b[c].length&&(delete a.b[c],a.h--))}};var W="closure_lm_"+(1E6*Math.random()|0),X={},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,e,d){if("array"==n(b))for(var f=0;f<b.length;f++)Ea(a,b[f],c,e,d);else if(c=Fa(c),wa(a))a.k.add(String(b),c,!0,e,d);else{if(!b)throw Error("Invalid event type");var f=!!e,g=Y(a);g||(a[W]=g=new V(a));c=g.add(b,c,!0,e,d);c.e||(e=Da(),c.e=e,e.src=a,e.c=c,a.addEventListener?a.addEventListener(b,e,f):a.attachEvent(b in
-X?X[b]:X[b]="on"+b,e),Ba++)}},Ha=function(a,b,c,e){var d=1;if(a=Y(a))if(b=a.b[b])for(b=D(b),a=0;a<b.length;a++){var f=b[a];f&&f.capture==c&&!f.d&&(d&=!1!==Ga(f,e))}return Boolean(d)},Ga=function(a,b){var c=a.c,e=a.f||a.src;if(a.g&&"number"!=typeof a&&a&&!a.d){var d=a.src;if(wa(d))Aa(d.k,a);else{var f=a.type,g=a.e;d.removeEventListener?d.removeEventListener(f,g,a.capture):d.detachEvent&&d.detachEvent(f in X?X[f]:X[f]="on"+f,g);Ba--;(f=Y(d))?(Aa(f,a),0==f.h&&(f.src=null,d[W]=null)):za(a)}}return c.call(e,
-b)},Ca=function(a,b){if(a.d)return!0;if(!ta){var c;if(!(c=b))t:{c=["window","event"];for(var e=m,d;d=c.shift();)if(null!=e[d])e=e[d];else{c=null;break t}c=e}d=c;c=new U(d,this);e=!0;if(!(0>d.keyCode||void 0!=d.returnValue)){t:{var f=!1;if(0==d.keyCode)try{d.keyCode=-1;break t}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.i&&0<=h;h--)c.currentTarget=d[h],e&=Ha(d[h],f,!0,c);for(h=0;!c.i&&h<d.length;h++)c.currentTarget=
-d[h],e&=Ha(d[h],f,!1,c)}return e}return Ga(a,new U(b,this))},Y=function(a){a=a[W];return a instanceof V?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 $=function(a,b,c){"number"==typeof a?(this.a=Ja(a,b||0,c||1),Z(this,c||1)):(b=typeof a,"object"==b&&null!=a||"function"==b?(this.a=Ja(a.getFullYear(),a.getMonth(),a.getDate()),Z(this,a.getDate())):(this.a=new Date(aa()),this.a.setHours(0),this.a.setMinutes(0),this.a.setSeconds(0),this.a.setMilliseconds(0)))},Ja=function(a,b,c){b=new Date(a,b,c);0<=a&&100>a&&b.setFullYear(b.getFullYear()-1900);return b};k=$.prototype;k.getFullYear=function(){return this.a.getFullYear()};k.getYear=function(){return this.getFullYear()};
+var la=M,ma={},Q=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]||"",h=d[f]||"",v=RegExp("(\\d*)(\\D*)","g"),p=RegExp("(\\d*)(\\D*)","g");do{var l=v.exec(g)||["","",""],C=p.exec(h)||["","",""];if(0==l[0].length&&0==C[0].length)break;b=x(0==l[1].length?0:parseInt(l[1],10),0==C[1].length?0:parseInt(C[1],10))||x(0==l[2].length,
+0==C[2].length)||x(l[2],C[2])}while(0==b)}b=ma[a]=0<=b}return b},na=m.document,oa=na&&J?ia()||("CSS1Compat"==na.compatMode?parseInt(la,10):5):void 0;!K&&!J||J&&J&&9<=oa||K&&Q("1.9.1");J&&Q("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 R=function(a,b,c){var d=document;c=c||d;var e=a&&"*"!=a?a.toUpperCase():"";if(c.querySelectorAll&&c.querySelector&&(e||b))return c.querySelectorAll(e+(b?"."+b:""));if(b&&c.getElementsByClassName){a=c.getElementsByClassName(b);if(e){c={};for(var f=d=0,g;g=a[f];f++)e==g.nodeName&&(c[d++]=g);c.length=d;return c}return a}a=c.getElementsByTagName(e||"*");if(b){c={};for(f=d=0;g=a[f];f++){var e=g.className,h;if(h="function"==typeof e.split)h=0<=B(e.split(/\s+/),b);h&&(c[d++]=g)}c.length=d;return c}return a};var S=function(a){S[" "](a);return a};S[" "]=function(){};var ta=!J||J&&9<=oa,ua=J&&!Q("9");!L||Q("528");K&&Q("1.9b")||J&&Q("8")||ha&&Q("9.5")||L&&Q("528");K&&!Q("8")||J&&Q("9");var T=function(a,b){this.type=a;this.currentTarget=this.target=b;this.defaultPrevented=this.j=!1};T.prototype.preventDefault=function(){this.defaultPrevented=!0};var U=function(a,b){T.call(this,a?a.type:"");this.relatedTarget=this.currentTarget=this.target=null;this.charCode=this.keyCode=this.button=this.screenY=this.screenX=this.clientY=this.clientX=this.offsetY=this.offsetX=0;this.metaKey=this.shiftKey=this.altKey=this.ctrlKey=!1;this.k=this.state=null;if(a){var c=this.type=a.type;this.target=a.target||a.srcElement;this.currentTarget=b;var d=a.relatedTarget;if(d){if(K){var e;t:{try{S(d.nodeName);e=!0;break t}catch(f){}e=!1}e||(d=null)}}else"mouseover"==
+c?d=a.fromElement:"mouseout"==c&&(d=a.toElement);this.relatedTarget=d;this.offsetX=L||void 0!==a.offsetX?a.offsetX:a.layerX;this.offsetY=L||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.k=a;a.defaultPrevented&&this.preventDefault()}};t(U,T);U.prototype.preventDefault=function(){U.o.preventDefault.call(this);var a=this.k;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.g=!!d;this.f=e;this.key=++xa;this.d=this.h=!1},za=function(a){a.d=!0;a.c=null;a.e=null;a.src=null;a.f=null};var V=function(a){this.src=a;this.b={};this.i=0};V.prototype.add=function(a,b,c,d,e){var f=a.toString();a=this.b[f];a||(a=this.b[f]=[],this.i++);var g;t:{for(g=0;g<a.length;++g){var h=a[g];if(!h.d&&h.c==b&&h.g==!!d&&h.f==e)break t}g=-1}-1<g?(b=a[g],c||(b.h=!1)):(b=new ya(b,this.src,f,!!d,e),b.h=c,a.push(b));return b};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.i--))}};var W="closure_lm_"+(1E6*Math.random()|0),X={},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.l.add(String(b),c,!0,d,e);else{if(!b)throw Error("Invalid event type");var f=!!d,g=Y(a);g||(a[W]=g=new V(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
+X?X[b]:X[b]="on"+b,d),Ba++)}},Ha=function(a,b,c,d){var e=1;if(a=Y(a))if(b=a.b[b])for(b=D(b),a=0;a<b.length;a++){var f=b[a];f&&f.g==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.h&&"number"!=typeof a&&a&&!a.d){var e=a.src;if(wa(e))Aa(e.l,a);else{var f=a.type,g=a.e;e.removeEventListener?e.removeEventListener(f,g,a.g):e.detachEvent&&e.detachEvent(f in X?X[f]:X[f]="on"+f,g);Ba--;(f=Y(e))?(Aa(f,a),0==f.i&&(f.src=null,e[W]=null)):za(a)}}return c.call(d,b)},Ca=
+function(a,b){if(a.d)return!0;if(!ta){var c;if(!(c=b))t:{c=["window","event"];for(var d=m,e;e=c.shift();)if(null!=d[e])d=d[e];else{c=null;break t}c=d}e=c;c=new U(e,this);d=!0;if(!(0>e.keyCode||void 0!=e.returnValue)){t:{var f=!1;if(0==e.keyCode)try{e.keyCode=-1;break t}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,h=e.length-1;!c.j&&0<=h;h--)c.currentTarget=e[h],d&=Ha(e[h],f,!0,c);for(h=0;!c.j&&h<e.length;h++)c.currentTarget=
+e[h],d&=Ha(e[h],f,!1,c)}return d}return Ga(a,new U(b,this))},Y=function(a){a=a[W];return a instanceof V?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 $=function(a,b,c){"number"==typeof a?(this.a=Ja(a,b||0,c||1),Z(this,c||1)):(b=typeof a,"object"==b&&null!=a||"function"==b?(this.a=Ja(a.getFullYear(),a.getMonth(),a.getDate()),Z(this,a.getDate())):(this.a=new Date(aa()),this.a.setHours(0),this.a.setMinutes(0),this.a.setSeconds(0),this.a.setMilliseconds(0)))},Ja=function(a,b,c){b=new Date(a,b,c);0<=a&&100>a&&b.setFullYear(b.getFullYear()-1900);return b};k=$.prototype;k.getFullYear=function(){return this.a.getFullYear()};k.getYear=function(){return this.getFullYear()};
 k.getMonth=function(){return this.a.getMonth()};k.getDate=function(){return this.a.getDate()};k.getTime=function(){return this.a.getTime()};k.getUTCHours=function(){return this.a.getUTCHours()};k.setFullYear=function(a){this.a.setFullYear(a)};k.setMonth=function(a){this.a.setMonth(a)};k.setDate=function(a){this.a.setDate(a)};
-k.add=function(a){if(a.o||a.m){var b=this.getMonth()+a.m+12*a.o,c=this.getYear()+Math.floor(b/12),b=b%12;0>b&&(b+=12);var e;t:{switch(b){case 1:e=0!=c%4||0==c%100&&0!=c%400?28:29;break t;case 5:case 8:case 10:case 3:e=30;break t}e=31}e=Math.min(e,this.getDate());this.setDate(1);this.setFullYear(c);this.setMonth(b);this.setDate(e)}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()),Z(this,a.getDate()))};k.p=function(){return[this.getFullYear(),w(this.getMonth()+1),w(this.getDate())].join("")+""};k.toString=function(){return this.p()};var Z=function(a,b){if(a.getDate()!=b){var c=a.getDate()<b?1:-1;a.a.setUTCHours(a.a.getUTCHours()+c)}};$.prototype.valueOf=function(){return this.a.valueOf()};new $(0,0,1);new $(9999,11,31);J||L&&Q("525");s("ae.init",function(){Ka();La();Ea(window,"load",function(){});Ma()});
-var Ka=function(){var a;a=document;if(a=q("ae-content")?a.getElementById("ae-content"):"ae-content"){a=R("table","ae-table-striped",a);for(var b=0,c;c=a[b];b++){c=R("tbody",null,c);for(var e=0,d;d=c[e];e++){d=R("tr",null,d);for(var f=0,g;g=d[f];f++)f%2&&qa(g,"ae-even")}}}},La=function(){var a=R(null,"ae-noscript",void 0);ca(D(a),function(a){sa(a,"ae-noscript")})},Ma=function(){m._gaq=m._gaq||[];m._gaq.push(function(){m._gaq._createAsyncTracker("UA-3739047-3","ae")._trackPageview()});(function(){var a=
+k.add=function(a){if(a.p||a.n){var b=this.getMonth()+a.n+12*a.p,c=this.getYear()+Math.floor(b/12),b=b%12;0>b&&(b+=12);var d;t:{switch(b){case 1:d=0!=c%4||0==c%100&&0!=c%400?28:29;break t;case 5:case 8:case 10:case 3:d=30;break t}d=31}d=Math.min(d,this.getDate());this.setDate(1);this.setFullYear(c);this.setMonth(b);this.setDate(d)}a.m&&(b=new Date(this.getYear(),this.getMonth(),this.getDate(),12),a=new Date(b.getTime()+864E5*a.m),this.setDate(1),this.setFullYear(a.getFullYear()),this.setMonth(a.getMonth()),
+this.setDate(a.getDate()),Z(this,a.getDate()))};k.q=function(){return[this.getFullYear(),w(this.getMonth()+1),w(this.getDate())].join("")+""};k.toString=function(){return this.q()};var Z=function(a,b){if(a.getDate()!=b){var c=a.getDate()<b?1:-1;a.a.setUTCHours(a.a.getUTCHours()+c)}};$.prototype.valueOf=function(){return this.a.valueOf()};new $(0,0,1);new $(9999,11,31);J||L&&Q("525");s("ae.init",function(){Ka();La();Ea(window,"load",function(){});Ma()});
+var Ka=function(){var a;a=document;if(a=q("ae-content")?a.getElementById("ae-content"):"ae-content"){a=R("table","ae-table-striped",a);for(var b=0,c;c=a[b];b++){c=R("tbody",null,c);for(var d=0,e;e=c[d];d++){e=R("tr",null,e);for(var f=0,g;g=e[f];f++)f%2&&qa(g,"ae-even")}}}},La=function(){var a=R(null,"ae-noscript",void 0);ca(D(a),function(a){sa(a,"ae-noscript")})},Ma=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)})()};s("ae.trackPageView",function(){m._gaq&&m._gaq._getAsyncTracker("ae")._trackPageview()});var Oa=function(a){if(void 0==a||null==a||0==a.length)return 0;a=Math.max.apply(Math,a);return Na(a)},Na=function(a){var b=5;2>b&&(b=2);b-=1;return Math.ceil(a/b)*b},Pa=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))},
-Qa=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=Oa(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(Pa,a,e,d))};s("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=Oa(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}}})});
-s("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",r(Pa,b,e,d))});
-s("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=Na(c);a.draw(d,{height:100,width:f,chartArea:{width:b},fontSize:10,isStacked:!0,vAxis:{minValue:0,maxValue:c,gridlines:{count:5}}})});
+Qa=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=Oa(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(Pa,a,d,e))};s("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=Oa(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}}})});
+s("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 h=Math.round(a[g]-c);f.addRow([h,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(Pa,b,d,e))});
+s("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=Na(c);a.draw(e,{height:100,width:f,chartArea:{width:b},fontSize:10,isStacked:!0,vAxis:{minValue:0,maxValue:c,gridlines:{count:5}}})});
 s("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"}})});s("ae.Charts.totalTimeBarChart",function(a,b,c,e){for(var d=[],f=0;f<b.length;f++)d[f]=b[f]+" milliseconds";Qa(a,b,d,c,e)});s("ae.Charts.rpcTimeBarChart",function(a,b,c,e,d){var f=[],g=[],h=c.indices,v=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]]=v[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}Qa(a,f,g,e,d)});})();
+legend:"none",tooltip:{trigger:"none"}})});s("ae.Charts.totalTimeBarChart",function(a,b,c,d){for(var e=[],f=0;f<b.length;f++)e[f]=b[f]+" milliseconds";Qa(a,b,e,c,d)});s("ae.Charts.rpcTimeBarChart",function(a,b,c,d,e){var f=[],g=[],h=c.indices,v=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]]=v[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}Qa(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 4f28240..06cbc9a 100644
--- a/google/appengine/ext/appstats/static/appstats_js.js
+++ b/google/appengine/ext/appstats/static/appstats_js.js
@@ -1,84 +1,85 @@
-/* Copyright 2008-10 Google Inc. All Rights Reserved. */ (function(){var f,l=this,aa=function(){},ba=function(a){a.fa=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"!=
+/* Copyright 2008-10 Google Inc. All Rights Reserved. */ (function(){var f,l=this,aa=function(){},ba=function(a){a.ga=function(){return a.Fb?a.Fb:a.Fb=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},ka=function(a){return a[ga]||
-(a[ga]=++ja)},ga="closure_uid_"+(1E9*Math.random()>>>0),ja=0,la=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)}},ma=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.e=b.prototype;a.prototype=new c;a.prototype.constructor=a;a.kc=
-function(a,c,g){var h=Array.prototype.slice.call(arguments,2);return b.prototype[c].apply(a,h)}};var na=function(a){Error.captureStackTrace?Error.captureStackTrace(this,na):this.stack=Error().stack||"";a&&(this.message=String(a))};p(na,Error);na.prototype.name="CustomError";var oa;var pa=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")},qa=function(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")},xa=function(a){if(!ra.test(a))return a;-1!=a.indexOf("&")&&(a=a.replace(sa,"&amp;"));-1!=a.indexOf("<")&&(a=a.replace(ta,"&lt;"));-1!=a.indexOf(">")&&(a=a.replace(ua,"&gt;"));-1!=a.indexOf('"')&&(a=a.replace(va,"&quot;"));-1!=a.indexOf("'")&&(a=a.replace(wa,"&#39;"));return a},sa=
+(a[ga]=++ja)},ga="closure_uid_"+(1E9*Math.random()>>>0),ja=0,la=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)}},ma=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.e=b.prototype;a.prototype=new c;a.prototype.constructor=a;a.mc=
+function(a,c,g){return b.prototype[c].apply(a,Array.prototype.slice.call(arguments,2))}};var na=function(a){if(Error.captureStackTrace)Error.captureStackTrace(this,na);else{var b=Error().stack;b&&(this.stack=b)}a&&(this.message=String(a))};p(na,Error);na.prototype.name="CustomError";var oa;var pa=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")},qa=function(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")},xa=function(a){if(!ra.test(a))return a;-1!=a.indexOf("&")&&(a=a.replace(sa,"&amp;"));-1!=a.indexOf("<")&&(a=a.replace(ta,"&lt;"));-1!=a.indexOf(">")&&(a=a.replace(ua,"&gt;"));-1!=a.indexOf('"')&&(a=a.replace(va,"&quot;"));-1!=a.indexOf("'")&&(a=a.replace(wa,"&#39;"));return a},sa=
 /&/g,ta=/</g,ua=/>/g,va=/"/g,wa=/'/g,ra=/[&<>"']/,ya=function(a,b){return a<b?-1:a>b?1:0};var za=function(a,b){b.unshift(a);na.call(this,pa.apply(null,b));b.shift()};p(za,na);za.prototype.name="AssertionError";var Aa=function(a,b,c){var d="Assertion failed";if(b)var d=d+(": "+b),e=c;else a&&(d+=": "+a,e=null);throw new za(""+d,e||[]);},r=function(a,b,c){a||Aa("",b,Array.prototype.slice.call(arguments,2))},Ba=function(a,b,c,d){a instanceof b||Aa("instanceof check failed.",c,Array.prototype.slice.call(arguments,3))};var s=Array.prototype,Ca=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},Da=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)},Ea=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},Fa=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<=Ca(a,b)},Ga=function(a,b){var c=Ca(a,b),d;if(d=0<=c)r(null!=a.length),s.splice.call(a,c,1);return d},Ha=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[]},Ja=function(a,b,c,d){r(null!=a.length);s.splice.apply(a,Ia(arguments,1))},Ia=function(a,b,c){r(null!=a.length);return 2>=arguments.length?s.slice.call(a,b):s.slice.call(a,b,c)};var Ka=function(a,b){for(var c in a)b.call(void 0,a[c],c,a)},La=function(a,b){for(var c in a)if(a[c]==b)return!0;return!1},Ma=function(a,b,c){if(b in a)throw Error('The object already contains the key "'+b+'"');a[b]=c},Na=function(a){var b={},c;for(c in a)b[a[c]]=c;return b},Oa="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),Pa=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<Oa.length;g++)c=
 Oa[g],Object.prototype.hasOwnProperty.call(d,c)&&(a[c]=d[c])}};var u,Qa,Ra,Sa,Ta=function(){return l.navigator?l.navigator.userAgent:null};Sa=Ra=Qa=u=!1;var Ua;if(Ua=Ta()){var Va=l.navigator;u=0==Ua.lastIndexOf("Opera",0);Qa=!u&&(-1!=Ua.indexOf("MSIE")||-1!=Ua.indexOf("Trident"));Ra=!u&&-1!=Ua.indexOf("WebKit");Sa=!u&&!Ra&&!Qa&&"Gecko"==Va.product}var Wa=u,v=Qa,w=Sa,x=Ra,Xa=l.navigator,y=-1!=(Xa&&Xa.platform||"").indexOf("Mac"),Ya=function(){var a=l.document;return a?a.documentMode:void 0},Za;
 t:{var $a="",ab;if(Wa&&l.opera)var bb=l.opera.version,$a="function"==typeof bb?bb():bb;else if(w?ab=/rv\:([^\);]+)(\)|;)/:v?ab=/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/:x&&(ab=/WebKit\/(\S+)/),ab)var cb=ab.exec(Ta()),$a=cb?cb[1]:"";if(v){var db=Ya();if(db>parseFloat($a)){Za=String(db);break t}}Za=$a}
-var eb=Za,fb={},z=function(a){var b;if(!(b=fb[a])){b=0;for(var c=qa(String(eb)).split("."),d=qa(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"),ha=RegExp("(\\d*)(\\D*)","g");do{var M=q.exec(h)||["","",""],ia=ha.exec(k)||["","",""];if(0==M[0].length&&0==ia[0].length)break;b=ya(0==M[1].length?0:parseInt(M[1],10),0==ia[1].length?0:parseInt(ia[1],10))||ya(0==M[2].length,0==ia[2].length)||ya(M[2],ia[2])}while(0==b)}b=fb[a]=0<=
+var eb=Za,fb={},z=function(a){var b;if(!(b=fb[a])){b=0;for(var c=qa(String(eb)).split("."),d=qa(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"),ha=RegExp("(\\d*)(\\D*)","g");do{var L=q.exec(h)||["","",""],ia=ha.exec(k)||["","",""];if(0==L[0].length&&0==ia[0].length)break;b=ya(0==L[1].length?0:parseInt(L[1],10),0==ia[1].length?0:parseInt(ia[1],10))||ya(0==L[2].length,0==ia[2].length)||ya(L[2],ia[2])}while(0==b)}b=fb[a]=0<=
 b}return b},gb=l.document,hb=gb&&v?Ya()||("CSS1Compat"==gb.compatMode?parseInt(eb,10):5):void 0;var ib=!v||v&&9<=hb;!w&&!v||v&&v&&9<=hb||w&&z("1.9.1");var jb=v&&!z("9");var kb=function(a){a=a.className;return m(a)&&a.match(/\S+/g)||[]},lb=function(a,b){for(var c=kb(a),d=Ia(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},nb=function(a,b){var c=kb(a),d=Ia(arguments,1),e=mb(c,d);a.className=e.join(" ");return e.length==c.length-d.length},mb=function(a,b){return Ea(a,function(a){return!t(b,a)})};var qb=function(a){return a?new ob(pb(a)):oa||(oa=new ob)},rb=function(a,b){return m(b)?a.getElementById(b):b},sb=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},ub=function(a,b){Ka(b,function(b,d){"style"==d?a.style.cssText=b:"class"==d?a.className=b:"for"==d?a.htmlFor=b:d in tb?a.setAttribute(tb[d],b):0==d.lastIndexOf("aria-",0)||0==d.lastIndexOf("data-",0)?a.setAttribute(d,b):a[d]=b})},tb={cellpadding:"cellPadding",cellspacing:"cellSpacing",colspan:"colSpan",frameborder:"frameBorder",height:"height",maxlength:"maxLength",role:"role",rowspan:"rowSpan",type:"type",usemap:"useMap",
 valign:"vAlign",width:"width"},wb=function(a,b,c){return vb(document,arguments)},vb=function(a,b){var c=b[0],d=b[1];if(!ib&&d&&(d.name||d.type)){c=["<",c];d.name&&c.push(' name="',xa(d.name),'"');if(d.type){c.push(' type="',xa(d.type),'"');var e={};Pa(e,d);delete e.type;d=e}c.push(">");c=c.join("")}c=a.createElement(c);d&&(m(d)?c.className=d:da(d)?lb.apply(null,[c].concat(d)):ub(c,d));2<b.length&&xb(a,c,b);return c},xb=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}Da(h?Ha(g):g,d)}}},yb=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},pb=function(a){return 9==a.nodeType?
 a:a.ownerDocument||a.document},zb=function(a,b){r(null!=a,"goog.dom.setTextContent expects a non-null value for node");if("textContent"in a)a.textContent=b;else if(3==a.nodeType)a.data=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(pb(a).createTextNode(String(b)))}},Ab={SCRIPT:1,STYLE:1,HEAD:1,IFRAME:1,OBJECT:1},Bb={IMG:" ",BR:"\n"},Cb=function(a){a=a.getAttributeNode("tabindex");
-return null!=a&&a.specified},Db=function(a){a=a.tabIndex;return"number"==typeof a&&0<=a&&32768>a},Eb=function(a,b,c){if(!(a.nodeName in Ab))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 Bb)b.push(Bb[a.nodeName]);else for(a=a.firstChild;a;)Eb(a,b,c),a=a.nextSibling},ob=function(a){this.Q=a||l.document||document};f=ob.prototype;f.kb=qb;f.a=function(a){return rb(this.Q,a)};f.o=function(a,b,c){return vb(this.Q,arguments)};
-f.createElement=function(a){return this.Q.createElement(a)};f.createTextNode=function(a){return this.Q.createTextNode(String(a))};f.appendChild=function(a,b){a.appendChild(b)};f.contains=yb;f.I=function(a){var b;(b="A"==a.tagName||"INPUT"==a.tagName||"TEXTAREA"==a.tagName||"SELECT"==a.tagName||"BUTTON"==a.tagName?!a.disabled&&(!Cb(a)||Db(a)):Cb(a)&&Db(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 Fb=function(a){Fb[" "](a);return a};Fb[" "]=aa;var Gb=!v||v&&9<=hb,Hb=!v||v&&9<=hb,Ib=v&&!z("9");!x||z("528");w&&z("1.9b")||v&&z("8")||Wa&&z("9.5")||x&&z("528");w&&!z("8")||v&&z("9");var Jb=function(){};Jb.prototype.Sb=!1;var A=function(a,b){this.type=a;this.currentTarget=this.target=b;this.defaultPrevented=this.aa=!1;this.xb=!0};A.prototype.stopPropagation=function(){this.aa=!0};A.prototype.preventDefault=function(){this.defaultPrevented=!0;this.xb=!1};var B=function(a,b){A.call(this,a?a.type:"");this.relatedTarget=this.currentTarget=this.target=null;this.charCode=this.keyCode=this.button=this.screenY=this.screenX=this.clientY=this.clientX=this.offsetY=this.offsetX=0;this.metaKey=this.shiftKey=this.altKey=this.ctrlKey=!1;this.state=null;this.ib=!1;this.O=null;if(a){var c=this.type=a.type;this.target=a.target||a.srcElement;this.currentTarget=b;var d=a.relatedTarget;if(d){if(w){var e;t:{try{Fb(d.nodeName);e=!0;break t}catch(g){}e=!1}e||(d=null)}}else"mouseover"==
+return null!=a&&a.specified},Db=function(a){a=a.tabIndex;return"number"==typeof a&&0<=a&&32768>a},Eb=function(a,b,c){if(!(a.nodeName in Ab))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 Bb)b.push(Bb[a.nodeName]);else for(a=a.firstChild;a;)Eb(a,b,c),a=a.nextSibling},ob=function(a){this.Q=a||l.document||document};f=ob.prototype;f.lb=qb;f.a=function(a){return rb(this.Q,a)};f.o=function(a,b,c){return vb(this.Q,arguments)};
+f.createElement=function(a){return this.Q.createElement(a)};f.createTextNode=function(a){return this.Q.createTextNode(String(a))};f.appendChild=function(a,b){a.appendChild(b)};f.contains=yb;f.I=function(a){var b;(b="A"==a.tagName||"INPUT"==a.tagName||"TEXTAREA"==a.tagName||"SELECT"==a.tagName||"BUTTON"==a.tagName?!a.disabled&&(!Cb(a)||Db(a)):Cb(a)&&Db(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 Fb=function(a){Fb[" "](a);return a};Fb[" "]=aa;var Gb=!v||v&&9<=hb,Hb=!v||v&&9<=hb,Ib=v&&!z("9");!x||z("528");w&&z("1.9b")||v&&z("8")||Wa&&z("9.5")||x&&z("528");w&&!z("8")||v&&z("9");var Jb=function(){};Jb.prototype.Ub=!1;var A=function(a,b){this.type=a;this.currentTarget=this.target=b;this.defaultPrevented=this.$=!1;this.wb=!0};A.prototype.stopPropagation=function(){this.$=!0};A.prototype.preventDefault=function(){this.defaultPrevented=!0;this.wb=!1};var B=function(a,b){A.call(this,a?a.type:"");this.relatedTarget=this.currentTarget=this.target=null;this.charCode=this.keyCode=this.button=this.screenY=this.screenX=this.clientY=this.clientX=this.offsetY=this.offsetX=0;this.metaKey=this.shiftKey=this.altKey=this.ctrlKey=!1;this.state=null;this.jb=!1;this.O=null;if(a){var c=this.type=a.type;this.target=a.target||a.srcElement;this.currentTarget=b;var d=a.relatedTarget;if(d){if(w){var e;t:{try{Fb(d.nodeName);e=!0;break t}catch(g){}e=!1}e||(d=null)}}else"mouseover"==
 c?d=a.fromElement:"mouseout"==c&&(d=a.toElement);this.relatedTarget=d;this.offsetX=x||void 0!==a.offsetX?a.offsetX:a.layerX;this.offsetY=x||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.ib=y?a.metaKey:a.ctrlKey;this.state=a.state;this.O=a;a.defaultPrevented&&this.preventDefault()}};p(B,A);var Kb=[1,4,2],Lb=function(a){return Gb?0==a.O.button:"click"==a.type?!0:!!(a.O.button&Kb[0])};B.prototype.stopPropagation=function(){B.e.stopPropagation.call(this);this.O.stopPropagation?this.O.stopPropagation():this.O.cancelBubble=!0};
-B.prototype.preventDefault=function(){B.e.preventDefault.call(this);var a=this.O;if(a.preventDefault)a.preventDefault();else if(a.returnValue=!1,Ib)try{if(a.ctrlKey||112<=a.keyCode&&123>=a.keyCode)a.keyCode=-1}catch(b){}};var Mb="closure_listenable_"+(1E6*Math.random()|0),Nb=function(a){try{return!(!a||!a[Mb])}catch(b){return!1}},Ob=0;var Pb=function(a,b,c,d,e){this.X=a;this.Da=null;this.src=b;this.type=c;this.capture=!!d;this.Ia=e;this.key=++Ob;this.ga=this.Ga=!1},Qb=function(a){a.ga=!0;a.X=null;a.Da=null;a.src=null;a.Ia=null};var C=function(a){this.src=a;this.m={};this.va=0};C.prototype.add=function(a,b,c,d,e){var g=this.m[a];g||(g=this.m[a]=[],this.va++);var h=Rb(g,b,d,e);-1<h?(a=g[h],c||(a.Ga=!1)):(a=new Pb(b,this.src,a,!!d,e),a.Ga=c,g.push(a));return a};C.prototype.remove=function(a,b,c,d){if(!(a in this.m))return!1;var e=this.m[a];b=Rb(e,b,c,d);return-1<b?(Qb(e[b]),r(null!=e.length),s.splice.call(e,b,1),0==e.length&&(delete this.m[a],this.va--),!0):!1};
-var Sb=function(a,b){var c=b.type;if(!(c in a.m))return!1;var d=Ga(a.m[c],b);d&&(Qb(b),0==a.m[c].length&&(delete a.m[c],a.va--));return d};C.prototype.$a=function(a){var b=0,c;for(c in this.m)if(!a||c==a){for(var d=this.m[c],e=0;e<d.length;e++)++b,Qb(d[e]);delete this.m[c];this.va--}return b};C.prototype.wa=function(a,b,c,d){a=this.m[a];var e=-1;a&&(e=Rb(a,b,c,d));return-1<e?a[e]:null};var Rb=function(a,b,c,d){for(var e=0;e<a.length;++e){var g=a[e];if(!g.ga&&g.X==b&&g.capture==!!c&&g.Ia==d)return e}return-1};var Tb="closure_lm_"+(1E6*Math.random()|0),D={},Ub=0,E=function(a,b,c,d,e){if(da(b)){for(var g=0;g<b.length;g++)E(a,b[g],c,d,e);return null}c=Vb(c);if(Nb(a))a=a.c(b,c,d,e);else{if(!b)throw Error("Invalid event type");var g=!!d,h=Wb(a);h||(a[Tb]=h=new C(a));c=h.add(b,c,!1,d,e);c.Da||(d=Xb(),c.Da=d,d.src=a,d.X=c,a.addEventListener?a.addEventListener(b,d,g):a.attachEvent(b in D?D[b]:D[b]="on"+b,d),Ub++);a=c}return a},Xb=function(){var a=Yb,b=Hb?function(c){return a.call(b.src,b.X,c)}:function(c){c=a.call(b.src,
-b.X,c);if(!c)return c};return b},Zb=function(a,b,c,d,e){if(da(b))for(var g=0;g<b.length;g++)Zb(a,b[g],c,d,e);else c=Vb(c),Nb(a)?a.u(b,c,d,e):a&&(a=Wb(a))&&(b=a.wa(b,c,!!d,e))&&F(b)},F=function(a){if("number"==typeof a||!a||a.ga)return!1;var b=a.src;if(Nb(b))return Sb(b.Z,a);var c=a.type,d=a.Da;b.removeEventListener?b.removeEventListener(c,d,a.capture):b.detachEvent&&b.detachEvent(c in D?D[c]:D[c]="on"+c,d);Ub--;(c=Wb(b))?(Sb(c,a),0==c.va&&(c.src=null,b[Tb]=null)):Qb(a);return!0},ac=function(a,b,c,
-d){var e=1;if(a=Wb(a))if(b=a.m[b])for(b=Ha(b),a=0;a<b.length;a++){var g=b[a];g&&g.capture==c&&!g.ga&&(e&=!1!==$b(g,d))}return Boolean(e)},$b=function(a,b){var c=a.X,d=a.Ia||a.src;a.Ga&&F(a);return c.call(d,b)},Yb=function(a,b){if(a.ga)return!0;if(!Hb){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 B(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.aa&&0<=k;k--)c.currentTarget=e[k],d&=ac(e[k],g,!0,c);for(k=0;!c.aa&&k<e.length;k++)c.currentTarget=e[k],d&=ac(e[k],g,!1,c)}return d}return $b(a,new B(b,this))},Wb=function(a){a=a[Tb];return a instanceof C?a:null},bc="__closure_events_fn_"+(1E9*Math.random()>>>0),Vb=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[bc]||(a[bc]=function(b){return a.handleEvent(b)})};var G=function(a){this.Db=a;this.Ma={}};p(G,Jb);var cc=[];G.prototype.c=function(a,b,c,d){da(b)||(cc[0]=b,b=cc);for(var e=0;e<b.length;e++){var g=E(a,b[e],c||this.handleEvent,d||!1,this.Db||this);if(!g)break;this.Ma[g.key]=g}return this};G.prototype.u=function(a,b,c,d,e){if(da(b))for(var g=0;g<b.length;g++)this.u(a,b[g],c,d,e);else c=c||this.handleEvent,e=e||this.Db||this,c=Vb(c),d=!!d,b=Nb(a)?a.wa(b,c,d,e):a?(a=Wb(a))?a.wa(b,c,d,e):null:null,b&&(F(b),delete this.Ma[b.key]);return this};
-G.prototype.$a=function(){Ka(this.Ma,F);this.Ma={}};G.prototype.handleEvent=function(){throw Error("EventHandler.handleEvent not implemented");};var H=function(){this.Z=new C(this);this.bc=this};p(H,Jb);H.prototype[Mb]=!0;f=H.prototype;f.mb=null;f.fb=function(a){this.mb=a};f.addEventListener=function(a,b,c,d){E(this,a,b,c,d)};f.removeEventListener=function(a,b,c,d){Zb(this,a,b,c,d)};
-f.dispatchEvent=function(a){dc(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 A(a,c);else if(a instanceof A)a.target=a.target||c;else{var e=a;a=new A(d,c);Pa(a,e)}var e=!0,g;if(b)for(var h=b.length-1;!a.aa&&0<=h;h--)g=a.currentTarget=b[h],e=ec(g,d,!0,a)&&e;a.aa||(g=a.currentTarget=c,e=ec(g,d,!0,a)&&e,a.aa||(e=ec(g,d,!1,a)&&e));if(b)for(h=0;!a.aa&&h<b.length;h++)g=a.currentTarget=b[h],e=ec(g,d,!1,a)&&e;return e};
-f.c=function(a,b,c,d){dc(this);return this.Z.add(String(a),b,!1,c,d)};f.u=function(a,b,c,d){return this.Z.remove(String(a),b,c,d)};var ec=function(a,b,c,d){b=a.Z.m[String(b)];if(!b)return!0;b=Ha(b);for(var e=!0,g=0;g<b.length;++g){var h=b[g];if(h&&!h.ga&&h.capture==c){var k=h.X,q=h.Ia||h.src;h.Ga&&Sb(a.Z,h);e=!1!==k.call(q,d)&&e}}return e&&!1!=d.xb};H.prototype.wa=function(a,b,c,d){return this.Z.wa(String(a),b,c,d)};var dc=function(a){r(a.Z,"Event target is not initialized. Did you call the superclass (goog.events.EventTarget) constructor?")};var I=function(a,b){a.style.display=b?"":"none"},fc=w?"MozUserSelect":x?"WebkitUserSelect":null,gc=function(a,b,c){c=c?null:a.getElementsByTagName("*");if(fc){if(b=b?"none":"",a.style[fc]=b,c){a=0;for(var d;d=c[a];a++)d.style[fc]=b}}else if(v||Wa)if(b=b?"on":"",a.setAttribute("unselectable",b),c)for(a=0;d=c[a];a++)d.setAttribute("unselectable",b)};var hc=function(){};ba(hc);hc.prototype.ec=0;var J=function(a){H.call(this);this.A=a||qb();this.sa=ic};p(J,H);J.prototype.dc=hc.fa();var ic=null,jc=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=J.prototype;f.ha=null;f.f=!1;f.d=null;f.sa=null;f.p=null;f.q=null;f.F=null;
-var kc=function(a){return a.ha||(a.ha=":"+(a.dc.ec++).toString(36))},lc=function(a,b){if(a.p&&a.p.F){var c=a.p.F,d=a.ha;d in c&&delete c[d];Ma(a.p.F,b,a)}a.ha=b};J.prototype.a=function(){return this.d};var mc=function(a){a.La||(a.La=new G(a));return a.La},oc=function(a,b){if(a==b)throw Error("Unable to set parent component");if(b&&a.p&&a.ha&&nc(a.p,a.ha)&&a.p!=b)throw Error("Unable to set parent component");a.p=b;J.e.fb.call(a,b)};f=J.prototype;f.getParent=function(){return this.p};
-f.fb=function(a){if(this.p&&this.p!=a)throw Error("Method not supported");J.e.fb.call(this,a)};f.kb=function(){return this.A};f.o=function(){this.d=this.A.createElement("div")};f.K=function(a){if(this.f)throw Error("Component already rendered");if(a&&this.Y(a)){var b=pb(a);this.A&&this.A.Q==b||(this.A=qb(a));this.Ya(a);this.D()}else throw Error("Invalid element to decorate");};f.Y=function(){return!0};f.Ya=function(a){this.d=a};f.D=function(){this.f=!0;pc(this,function(a){!a.f&&a.a()&&a.D()})};
-f.ca=function(){pc(this,function(a){a.f&&a.ca()});this.La&&this.La.$a();this.f=!1};f.Ca=function(a,b){this.Ua(a,qc(this),b)};
-f.Ua=function(a,b,c){r(!!a,"Provided element must not be null.");if(a.f&&(c||!this.f))throw Error("Component already rendered");if(0>b||b>qc(this))throw Error("Child component index out of bounds");this.F&&this.q||(this.F={},this.q=[]);if(a.getParent()==this){var d=kc(a);this.F[d]=a;Ga(this.q,a)}else Ma(this.F,kc(a),a);oc(a,this);Ja(this.q,b,0,a);if(a.f&&this.f&&a.getParent()==this)c=this.B(),c.insertBefore(a.a(),c.childNodes[b]||null);else if(c){this.d||this.o();c=K(this,b+1);b=this.B();c=c?c.d:
+a.metaKey;this.jb=y?a.metaKey:a.ctrlKey;this.state=a.state;this.O=a;a.defaultPrevented&&this.preventDefault()}};p(B,A);var Kb=[1,4,2],Lb=function(a){return Gb?0==a.O.button:"click"==a.type?!0:!!(a.O.button&Kb[0])};B.prototype.stopPropagation=function(){B.e.stopPropagation.call(this);this.O.stopPropagation?this.O.stopPropagation():this.O.cancelBubble=!0};
+B.prototype.preventDefault=function(){B.e.preventDefault.call(this);var a=this.O;if(a.preventDefault)a.preventDefault();else if(a.returnValue=!1,Ib)try{if(a.ctrlKey||112<=a.keyCode&&123>=a.keyCode)a.keyCode=-1}catch(b){}};var Mb="closure_listenable_"+(1E6*Math.random()|0),Nb=function(a){try{return!(!a||!a[Mb])}catch(b){return!1}},Ob=0;var Pb=function(a,b,c,d,e){this.W=a;this.Da=null;this.src=b;this.type=c;this.Ea=!!d;this.Ga=e;this.key=++Ob;this.fa=this.Fa=!1},Qb=function(a){a.fa=!0;a.W=null;a.Da=null;a.src=null;a.Ga=null};var C=function(a){this.src=a;this.m={};this.ua=0};C.prototype.add=function(a,b,c,d,e){var g=a.toString();a=this.m[g];a||(a=this.m[g]=[],this.ua++);var h=Rb(a,b,d,e);-1<h?(b=a[h],c||(b.Fa=!1)):(b=new Pb(b,this.src,g,!!d,e),b.Fa=c,a.push(b));return b};C.prototype.remove=function(a,b,c,d){a=a.toString();if(!(a in this.m))return!1;var e=this.m[a];b=Rb(e,b,c,d);return-1<b?(Qb(e[b]),r(null!=e.length),s.splice.call(e,b,1),0==e.length&&(delete this.m[a],this.ua--),!0):!1};
+var Sb=function(a,b){var c=b.type;if(!(c in a.m))return!1;var d=Ga(a.m[c],b);d&&(Qb(b),0==a.m[c].length&&(delete a.m[c],a.ua--));return d};C.prototype.ab=function(a){a=a&&a.toString();var b=0,c;for(c in this.m)if(!a||c==a){for(var d=this.m[c],e=0;e<d.length;e++)++b,Qb(d[e]);delete this.m[c];this.ua--}return b};C.prototype.wa=function(a,b,c,d){a=this.m[a.toString()];var e=-1;a&&(e=Rb(a,b,c,d));return-1<e?a[e]:null};
+var Rb=function(a,b,c,d){for(var e=0;e<a.length;++e){var g=a[e];if(!g.fa&&g.W==b&&g.Ea==!!c&&g.Ga==d)return e}return-1};var Tb="closure_lm_"+(1E6*Math.random()|0),D={},Ub=0,E=function(a,b,c,d,e){if(da(b)){for(var g=0;g<b.length;g++)E(a,b[g],c,d,e);return null}c=Vb(c);if(Nb(a))a=a.c(b,c,d,e);else{if(!b)throw Error("Invalid event type");var g=!!d,h=Wb(a);h||(a[Tb]=h=new C(a));c=h.add(b,c,!1,d,e);c.Da||(d=Xb(),c.Da=d,d.src=a,d.W=c,a.addEventListener?a.addEventListener(b,d,g):a.attachEvent(b in D?D[b]:D[b]="on"+b,d),Ub++);a=c}return a},Xb=function(){var a=Yb,b=Hb?function(c){return a.call(b.src,b.W,c)}:function(c){c=a.call(b.src,
+b.W,c);if(!c)return c};return b},Zb=function(a,b,c,d,e){if(da(b))for(var g=0;g<b.length;g++)Zb(a,b[g],c,d,e);else c=Vb(c),Nb(a)?a.u(b,c,d,e):a&&(a=Wb(a))&&(b=a.wa(b,c,!!d,e))&&F(b)},F=function(a){if("number"==typeof a||!a||a.fa)return!1;var b=a.src;if(Nb(b))return Sb(b.Y,a);var c=a.type,d=a.Da;b.removeEventListener?b.removeEventListener(c,d,a.Ea):b.detachEvent&&b.detachEvent(c in D?D[c]:D[c]="on"+c,d);Ub--;(c=Wb(b))?(Sb(c,a),0==c.ua&&(c.src=null,b[Tb]=null)):Qb(a);return!0},ac=function(a,b,c,d){var e=
+1;if(a=Wb(a))if(b=a.m[b])for(b=Ha(b),a=0;a<b.length;a++){var g=b[a];g&&g.Ea==c&&!g.fa&&(e&=!1!==$b(g,d))}return Boolean(e)},$b=function(a,b){var c=a.W,d=a.Ga||a.src;a.Fa&&F(a);return c.call(d,b)},Yb=function(a,b){if(a.fa)return!0;if(!Hb){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 B(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.$&&0<=k;k--)c.currentTarget=e[k],d&=ac(e[k],g,!0,c);for(k=0;!c.$&&k<e.length;k++)c.currentTarget=e[k],d&=ac(e[k],g,!1,c)}return d}return $b(a,new B(b,this))},Wb=function(a){a=a[Tb];return a instanceof C?a:null},bc="__closure_events_fn_"+(1E9*Math.random()>>>0),Vb=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[bc]||(a[bc]=
+function(b){return a.handleEvent(b)})};var G=function(a){this.Eb=a;this.Na={}};p(G,Jb);var cc=[];G.prototype.c=function(a,b,c,d){da(b)||(cc[0]=b,b=cc);for(var e=0;e<b.length;e++){var g=E(a,b[e],c||this.handleEvent,d||!1,this.Eb||this);if(!g)break;this.Na[g.key]=g}return this};G.prototype.u=function(a,b,c,d,e){if(da(b))for(var g=0;g<b.length;g++)this.u(a,b[g],c,d,e);else c=c||this.handleEvent,e=e||this.Eb||this,c=Vb(c),d=!!d,b=Nb(a)?a.wa(b,c,d,e):a?(a=Wb(a))?a.wa(b,c,d,e):null:null,b&&(F(b),delete this.Na[b.key]);return this};
+G.prototype.ab=function(){Ka(this.Na,F);this.Na={}};G.prototype.handleEvent=function(){throw Error("EventHandler.handleEvent not implemented");};var H=function(){this.Y=new C(this);this.dc=this};p(H,Jb);H.prototype[Mb]=!0;f=H.prototype;f.nb=null;f.gb=function(a){this.nb=a};f.addEventListener=function(a,b,c,d){E(this,a,b,c,d)};f.removeEventListener=function(a,b,c,d){Zb(this,a,b,c,d)};
+f.dispatchEvent=function(a){dc(this);var b,c=this.nb;if(c){b=[];for(var d=1;c;c=c.nb)b.push(c),r(1E3>++d,"infinite loop")}c=this.dc;d=a.type||a;if(m(a))a=new A(a,c);else if(a instanceof A)a.target=a.target||c;else{var e=a;a=new A(d,c);Pa(a,e)}var e=!0,g;if(b)for(var h=b.length-1;!a.$&&0<=h;h--)g=a.currentTarget=b[h],e=ec(g,d,!0,a)&&e;a.$||(g=a.currentTarget=c,e=ec(g,d,!0,a)&&e,a.$||(e=ec(g,d,!1,a)&&e));if(b)for(h=0;!a.$&&h<b.length;h++)g=a.currentTarget=b[h],e=ec(g,d,!1,a)&&e;return e};
+f.c=function(a,b,c,d){dc(this);return this.Y.add(String(a),b,!1,c,d)};f.u=function(a,b,c,d){return this.Y.remove(String(a),b,c,d)};var ec=function(a,b,c,d){b=a.Y.m[String(b)];if(!b)return!0;b=Ha(b);for(var e=!0,g=0;g<b.length;++g){var h=b[g];if(h&&!h.fa&&h.Ea==c){var k=h.W,q=h.Ga||h.src;h.Fa&&Sb(a.Y,h);e=!1!==k.call(q,d)&&e}}return e&&!1!=d.wb};H.prototype.wa=function(a,b,c,d){return this.Y.wa(String(a),b,c,d)};var dc=function(a){r(a.Y,"Event target is not initialized. Did you call the superclass (goog.events.EventTarget) constructor?")};var I=function(a,b){a.style.display=b?"":"none"},fc=w?"MozUserSelect":x?"WebkitUserSelect":null,gc=function(a,b,c){c=c?null:a.getElementsByTagName("*");if(fc){if(b=b?"none":"",a.style[fc]=b,c){a=0;for(var d;d=c[a];a++)d.style[fc]=b}}else if(v||Wa)if(b=b?"on":"",a.setAttribute("unselectable",b),c)for(a=0;d=c[a];a++)d.setAttribute("unselectable",b)};var hc=function(){};ba(hc);hc.prototype.gc=0;var J=function(a){H.call(this);this.A=a||qb();this.sa=ic};p(J,H);J.prototype.fc=hc.ga();var ic=null,jc=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=J.prototype;f.ha=null;f.f=!1;f.d=null;f.sa=null;f.p=null;f.q=null;f.F=null;
+var kc=function(a){return a.ha||(a.ha=":"+(a.fc.gc++).toString(36))},lc=function(a,b){if(a.p&&a.p.F){var c=a.p.F,d=a.ha;d in c&&delete c[d];Ma(a.p.F,b,a)}a.ha=b};J.prototype.a=function(){return this.d};var mc=function(a){a.Ma||(a.Ma=new G(a));return a.Ma},oc=function(a,b){if(a==b)throw Error("Unable to set parent component");if(b&&a.p&&a.ha&&nc(a.p,a.ha)&&a.p!=b)throw Error("Unable to set parent component");a.p=b;J.e.gb.call(a,b)};f=J.prototype;f.getParent=function(){return this.p};
+f.gb=function(a){if(this.p&&this.p!=a)throw Error("Method not supported");J.e.gb.call(this,a)};f.lb=function(){return this.A};f.o=function(){this.d=this.A.createElement("div")};f.K=function(a){if(this.f)throw Error("Component already rendered");if(a&&this.X(a)){var b=pb(a);this.A&&this.A.Q==b||(this.A=qb(a));this.Za(a);this.D()}else throw Error("Invalid element to decorate");};f.X=function(){return!0};f.Za=function(a){this.d=a};f.D=function(){this.f=!0;pc(this,function(a){!a.f&&a.a()&&a.D()})};
+f.ba=function(){pc(this,function(a){a.f&&a.ba()});this.Ma&&this.Ma.ab();this.f=!1};f.Ca=function(a,b){this.Va(a,qc(this),b)};
+f.Va=function(a,b,c){r(!!a,"Provided element must not be null.");if(a.f&&(c||!this.f))throw Error("Component already rendered");if(0>b||b>qc(this))throw Error("Child component index out of bounds");this.F&&this.q||(this.F={},this.q=[]);if(a.getParent()==this){var d=kc(a);this.F[d]=a;Ga(this.q,a)}else Ma(this.F,kc(a),a);oc(a,this);Ja(this.q,b,0,a);if(a.f&&this.f&&a.getParent()==this)c=this.B(),c.insertBefore(a.a(),c.childNodes[b]||null);else if(c){this.d||this.o();c=K(this,b+1);b=this.B();c=c?c.d:
 null;if(a.f)throw Error("Component already rendered");a.d||a.o();b?b.insertBefore(a.d,c||null):a.A.Q.body.appendChild(a.d);a.p&&!a.p.f||a.D()}else this.f&&!a.f&&a.d&&a.d.parentNode&&1==a.d.parentNode.nodeType&&a.D()};f.B=function(){return this.d};
 var rc=function(a){if(null==a.sa){var b=a.f?a.d:a.A.Q.body,c;t:{c=pb(b);if(c.defaultView&&c.defaultView.getComputedStyle&&(c=c.defaultView.getComputedStyle(b,null))){c=c.direction||c.getPropertyValue("direction")||"";break t}c=""}a.sa="rtl"==(c||(b.currentStyle?b.currentStyle.direction:null)||b.style&&b.style.direction)}return a.sa};J.prototype.pa=function(a){if(this.f)throw Error("Component already rendered");this.sa=a};
 var qc=function(a){return a.q?a.q.length:0},nc=function(a,b){var c;a.F&&b?(c=a.F,c=(b in c?c[b]:void 0)||null):c=null;return c},K=function(a,b){return a.q?a.q[b]||null:null},pc=function(a,b,c){a.q&&Da(a.q,b,c)},sc=function(a,b){return a.q&&b?Ca(a.q,b):-1};
-J.prototype.removeChild=function(a,b){if(a){var c=m(a)?a:kc(a);a=nc(this,c);if(c&&a){var d=this.F;c in d&&delete d[c];Ga(this.q,a);b&&(a.ca(),a.d&&(c=a.d)&&c.parentNode&&c.parentNode.removeChild(c));oc(a,null)}}if(!a)throw Error("Child is not in parent component");return a};var tc,uc={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 vc={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 wc=function(a,b){b?(r(La(vc,b),"No such ARIA role "+b),a.setAttribute("role",b)):a.removeAttribute("role")},yc=function(a,b,c){ea(c)&&(c=c.join(" "));var d=xc(b);""===c||void 0==c?(tc||(tc={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=tc,b in c?a.setAttribute(d,c[b]):a.removeAttribute(d)):a.setAttribute(d,
+J.prototype.removeChild=function(a,b){if(a){var c=m(a)?a:kc(a);a=nc(this,c);if(c&&a){var d=this.F;c in d&&delete d[c];Ga(this.q,a);b&&(a.ba(),a.d&&(c=a.d)&&c.parentNode&&c.parentNode.removeChild(c));oc(a,null)}}if(!a)throw Error("Child is not in parent component");return a};var tc,uc={nc:"activedescendant",sc:"atomic",tc:"autocomplete",vc:"busy",yc:"checked",Dc:"controls",Fc:"describedby",Ic:"disabled",Kc:"dropeffect",Lc:"expanded",Mc:"flowto",Oc:"grabbed",Sc:"haspopup",Uc:"hidden",Wc:"invalid",Xc:"label",Yc:"labelledby",Zc:"level",dd:"live",od:"multiline",pd:"multiselectable",td:"orientation",ud:"owns",vd:"posinset",xd:"pressed",Bd:"readonly",Dd:"relevant",Ed:"required",Kd:"selected",Md:"setsize",Od:"sort",ae:"valuemax",be:"valuemin",ce:"valuenow",de:"valuetext"};var vc={oc:"alert",pc:"alertdialog",qc:"application",rc:"article",uc:"banner",wc:"button",xc:"checkbox",zc:"columnheader",Ac:"combobox",Bc:"complementary",Cc:"contentinfo",Ec:"definition",Gc:"dialog",Hc:"directory",Jc:"document",Nc:"form",Pc:"grid",Qc:"gridcell",Rc:"group",Tc:"heading",Vc:"img",$c:"link",ad:"list",bd:"listbox",cd:"listitem",ed:"log",fd:"main",gd:"marquee",hd:"math",jd:"menu",kd:"menubar",ld:"menuitem",md:"menuitemcheckbox",nd:"menuitemradio",qd:"navigation",rd:"note",sd:"option",
+wd:"presentation",yd:"progressbar",zd:"radio",Ad:"radiogroup",Cd:"region",Fd:"row",Gd:"rowgroup",Hd:"rowheader",Id:"scrollbar",Jd:"search",Ld:"separator",Nd:"slider",Pd:"spinbutton",Qd:"status",Rd:"tab",Sd:"tablist",Td:"tabpanel",Ud:"textbox",Vd:"timer",Wd:"toolbar",Xd:"tooltip",Yd:"tree",Zd:"treegrid",$d:"treeitem"};var wc=function(a,b){b?(r(La(vc,b),"No such ARIA role "+b),a.setAttribute("role",b)):a.removeAttribute("role")},yc=function(a,b,c){ea(c)&&(c=c.join(" "));var d=xc(b);""===c||void 0==c?(tc||(tc={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=tc,b in c?a.setAttribute(d,c[b]):a.removeAttribute(d)):a.setAttribute(d,
 c)},xc=function(a){r(a,"ARIA attribute cannot be empty.");r(La(uc,a),"No such ARIA attribute "+a);return"aria-"+a};var Bc=function(a,b,c,d,e){if(!(v||x&&z("525")))return!0;if(y&&e)return zc(a);if(e&&!d)return!1;"number"==typeof b&&(b=Ac(b));if(!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<=hb);case 27:return!x}return zc(a)},zc=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}},Ac=function(a){if(w)a=Cc(a);else if(y&&x)t:switch(a){case 93:a=91;break t}return a},Cc=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 L=function(a,b){H.call(this);a&&Dc(this,a,b)};p(L,H);f=L.prototype;f.d=null;f.Ea=null;f.Xa=null;f.Fa=null;f.r=-1;f.N=-1;f.jb=!1;
+default:return!1}},Ac=function(a){if(w)a=Cc(a);else if(y&&x)t:switch(a){case 93:a=91;break t}return a},Cc=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 M=function(a,b){H.call(this);a&&Dc(this,a,b)};p(M,H);f=M.prototype;f.d=null;f.Ha=null;f.Ya=null;f.Ia=null;f.r=-1;f.N=-1;f.kb=!1;
 var Ec={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},Fc={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},Gc=v||x&&z("525"),Hc=y&&w;
-L.prototype.Qb=function(a){x&&(17==this.r&&!a.ctrlKey||18==this.r&&!a.altKey||y&&91==this.r&&!a.metaKey)&&(this.N=this.r=-1);-1==this.r&&(a.ctrlKey&&17!=a.keyCode?this.r=17:a.altKey&&18!=a.keyCode?this.r=18:a.metaKey&&91!=a.keyCode&&(this.r=91));Gc&&!Bc(a.keyCode,this.r,a.shiftKey,a.ctrlKey,a.altKey)?this.handleEvent(a):(this.N=Ac(a.keyCode),Hc&&(this.jb=a.altKey))};L.prototype.Rb=function(a){this.N=this.r=-1;this.jb=a.altKey};
-L.prototype.handleEvent=function(a){var b=a.O,c,d,e=b.altKey;v&&"keypress"==a.type?(c=this.N,d=13!=c&&27!=c?b.keyCode:0):x&&"keypress"==a.type?(c=this.N,d=0<=b.charCode&&63232>b.charCode&&zc(c)?b.charCode:0):Wa?(c=this.N,d=zc(c)?b.keyCode:0):(c=b.keyCode||this.N,d=b.charCode||0,Hc&&(e=this.jb),y&&63==d&&224==c&&(c=191));var g=c=Ac(c),h=b.keyIdentifier;c?63232<=c&&c in Ec?g=Ec[c]:25==c&&a.shiftKey&&(g=9):h&&h in Fc&&(g=Fc[h]);a=g==this.r;this.r=g;b=new Ic(g,d,a,b);b.altKey=e;this.dispatchEvent(b)};
-L.prototype.a=function(){return this.d};var Dc=function(a,b,c){a.Fa&&a.detach();a.d=b;a.Ea=E(a.d,"keypress",a,c);a.Xa=E(a.d,"keydown",a.Qb,c,a);a.Fa=E(a.d,"keyup",a.Rb,c,a)};L.prototype.detach=function(){this.Ea&&(F(this.Ea),F(this.Xa),F(this.Fa),this.Fa=this.Xa=this.Ea=null);this.d=null;this.N=this.r=-1};var Ic=function(a,b,c,d){B.call(this,d);this.type="key";this.keyCode=a;this.charCode=b;this.repeat=c};p(Ic,B);var Jc=function(a){if(a.classList)return a.classList;a=a.className;return m(a)&&a.match(/\S+/g)||[]},Kc=function(a,b){return a.classList?a.classList.contains(b):t(Jc(a),b)},Lc=function(a,b){a.classList?a.classList.add(b):Kc(a,b)||(a.className+=0<a.className.length?" "+b:b)},Mc=function(a,b){a.classList?a.classList.remove(b):Kc(a,b)&&(a.className=Ea(Jc(a),function(a){return a!=b}).join(" "))};var Oc=function(a,b){if(!a)throw Error("Invalid class name "+a);if(!n(b))throw Error("Invalid decorator function "+b);Nc[a]=b},Pc={},Nc={};var N=function(){};ba(N);N.prototype.T=function(){};var Qc=function(a,b){a&&(a.tabIndex=b?0:-1)};f=N.prototype;f.o=function(a){return a.kb().o("div",this.ta(a).join(" "))};f.B=function(a){return a};f.Y=function(a){return"DIV"==a.tagName};f.K=function(a,b){b.id&&lc(a,b.id);var c=this.v(),d=!1,e=Jc(b);e&&Da(e,function(b){b==c?d=!0:b&&this.ab(a,b,c)},this);d||Lc(b,c);Rc(a,this.B(b));return b};
-f.ab=function(a,b,c){b==c+"-disabled"?a.qa(!1):b==c+"-horizontal"?Sc(a,"horizontal"):b==c+"-vertical"&&Sc(a,"vertical")};var Rc=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:{var g=void 0;e=Jc(c);for(var h=0,k=e.length;h<k;h++)if(g=e[h],g=g in Nc?Nc[g]():null){e=g;break t}e=null}e&&(e.d=c,a.isEnabled()||e.qa(!1),a.Ca(e),e.K(c))}else c.nodeValue&&""!=qa(c.nodeValue)||b.removeChild(c);c=d}};
-N.prototype.Na=function(a){a=a.a();r(a,"The container DOM element cannot be null.");gc(a,!0,w);v&&(a.hideFocus=!0);var b=this.T();b&&wc(a,b)};N.prototype.j=function(a){return a.a()};N.prototype.v=function(){return"goog-container"};N.prototype.ta=function(a){var b=this.v(),c=[b,"horizontal"==a.L?b+"-horizontal":b+"-vertical"];a.isEnabled()||c.push(b+"-disabled");return c};var O=function(){},Tc;ba(O);f=O.prototype;f.T=function(){};f.o=function(a){var b=a.kb().o("div",this.ta(a).join(" "),a.Ba);Uc(a,b);return b};f.B=function(a){return a};f.ra=function(a,b,c){if(a=a.a?a.a():a)if(v&&!z("7")){var d=Vc(kb(a),b);d.push(b);la(c?lb:nb,a).apply(null,d)}else c?lb(a,b):nb(a,b)};f.Y=function(){return!0};
-f.K=function(a,b){b.id&&lc(a,b.id);var c=this.B(b);c&&c.firstChild?Wc(a,c.firstChild.nextSibling?Ha(c.childNodes):c.firstChild):a.Ba=null;var d=0,e=this.v(),g=this.v(),h=!1,k=!1,c=!1,q=kb(b);Da(q,function(a){if(h||a!=e)if(k||a!=g){var b=d;this.tb||(this.Ha||Xc(this),this.tb=Na(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 ha=a.G;ha&&q.push.apply(q,ha);if(v&&!z("7")){var M=Vc(q);0<M.length&&(q.push.apply(q,
-M),c=!0)}if(!h||!k||ha||c)b.className=q.join(" ");Uc(a,b);return b};f.Na=function(a){rc(a)&&this.pa(a.a(),!0);a.isEnabled()&&this.na(a,a.s())};var Yc=function(a,b,c){if(a=c||a.T())r(b,"The element passed as a first parameter cannot be null."),wc(b,a)},Uc=function(a,b){r(a);r(b);a.s()||yc(b,"hidden",!a.s());a.isEnabled()||Zc(b,1,!a.isEnabled());a.l&8&&Zc(b,8,!!(a.g&8));a.l&16&&Zc(b,16,!!(a.g&16));a.l&64&&Zc(b,64,!!(a.g&64))};f=O.prototype;f.za=function(a,b){gc(a,!b,!v&&!Wa)};
+M.prototype.Sb=function(a){x&&(17==this.r&&!a.ctrlKey||18==this.r&&!a.altKey||y&&91==this.r&&!a.metaKey)&&(this.N=this.r=-1);-1==this.r&&(a.ctrlKey&&17!=a.keyCode?this.r=17:a.altKey&&18!=a.keyCode?this.r=18:a.metaKey&&91!=a.keyCode&&(this.r=91));Gc&&!Bc(a.keyCode,this.r,a.shiftKey,a.ctrlKey,a.altKey)?this.handleEvent(a):(this.N=Ac(a.keyCode),Hc&&(this.kb=a.altKey))};M.prototype.Tb=function(a){this.N=this.r=-1;this.kb=a.altKey};
+M.prototype.handleEvent=function(a){var b=a.O,c,d,e=b.altKey;v&&"keypress"==a.type?(c=this.N,d=13!=c&&27!=c?b.keyCode:0):x&&"keypress"==a.type?(c=this.N,d=0<=b.charCode&&63232>b.charCode&&zc(c)?b.charCode:0):Wa?(c=this.N,d=zc(c)?b.keyCode:0):(c=b.keyCode||this.N,d=b.charCode||0,Hc&&(e=this.kb),y&&63==d&&224==c&&(c=191));var g=c=Ac(c),h=b.keyIdentifier;c?63232<=c&&c in Ec?g=Ec[c]:25==c&&a.shiftKey&&(g=9):h&&h in Fc&&(g=Fc[h]);a=g==this.r;this.r=g;b=new Ic(g,d,a,b);b.altKey=e;this.dispatchEvent(b)};
+M.prototype.a=function(){return this.d};var Dc=function(a,b,c){a.Ia&&a.detach();a.d=b;a.Ha=E(a.d,"keypress",a,c);a.Ya=E(a.d,"keydown",a.Sb,c,a);a.Ia=E(a.d,"keyup",a.Tb,c,a)};M.prototype.detach=function(){this.Ha&&(F(this.Ha),F(this.Ya),F(this.Ia),this.Ia=this.Ya=this.Ha=null);this.d=null;this.N=this.r=-1};var Ic=function(a,b,c,d){B.call(this,d);this.type="key";this.keyCode=a;this.charCode=b;this.repeat=c};p(Ic,B);var Jc=function(a){if(a.classList)return a.classList;a=a.className;return m(a)&&a.match(/\S+/g)||[]},Kc=function(a,b){return a.classList?a.classList.contains(b):t(Jc(a),b)},Lc=function(a,b){a.classList?a.classList.add(b):Kc(a,b)||(a.className+=0<a.className.length?" "+b:b)},Mc=function(a,b){a.classList?a.classList.remove(b):Kc(a,b)&&(a.className=Ea(Jc(a),function(a){return a!=b}).join(" "))};var Oc=function(a,b){if(!a)throw Error("Invalid class name "+a);if(!n(b))throw Error("Invalid decorator function "+b);Nc[a]=b},Pc={},Nc={};var N=function(a){this.Gb=a};ba(N);N.prototype.da=function(){return this.Gb};var Qc=function(a,b){a&&(a.tabIndex=b?0:-1)};f=N.prototype;f.o=function(a){return a.lb().o("div",this.ta(a).join(" "))};f.B=function(a){return a};f.X=function(a){return"DIV"==a.tagName};f.K=function(a,b){b.id&&lc(a,b.id);var c=this.v(),d=!1,e=Jc(b);e&&Da(e,function(b){b==c?d=!0:b&&this.bb(a,b,c)},this);d||Lc(b,c);Rc(a,this.B(b));return b};
+f.bb=function(a,b,c){b==c+"-disabled"?a.qa(!1):b==c+"-horizontal"?Sc(a,"horizontal"):b==c+"-vertical"&&Sc(a,"vertical")};var Rc=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:{var g=void 0;e=Jc(c);for(var h=0,k=e.length;h<k;h++)if(g=e[h],g=g in Nc?Nc[g]():null){e=g;break t}e=null}e&&(e.d=c,a.isEnabled()||e.qa(!1),a.Ca(e),e.K(c))}else c.nodeValue&&""!=qa(c.nodeValue)||b.removeChild(c);c=d}};
+N.prototype.Oa=function(a){a=a.a();r(a,"The container DOM element cannot be null.");gc(a,!0,w);v&&(a.hideFocus=!0);var b=this.da();b&&wc(a,b)};N.prototype.j=function(a){return a.a()};N.prototype.v=function(){return"goog-container"};N.prototype.ta=function(a){var b=this.v(),c=[b,"horizontal"==a.L?b+"-horizontal":b+"-vertical"];a.isEnabled()||c.push(b+"-disabled");return c};var O=function(){},Tc;ba(O);f=O.prototype;f.da=function(){};f.o=function(a){var b=a.lb().o("div",this.ta(a).join(" "),a.Ba);Uc(a,b);return b};f.B=function(a){return a};f.ra=function(a,b,c){if(a=a.a?a.a():a)if(v&&!z("7")){var d=Vc(kb(a),b);d.push(b);la(c?lb:nb,a).apply(null,d)}else c?lb(a,b):nb(a,b)};f.X=function(){return!0};
+f.K=function(a,b){b.id&&lc(a,b.id);var c=this.B(b);c&&c.firstChild?Wc(a,c.firstChild.nextSibling?Ha(c.childNodes):c.firstChild):a.Ba=null;var d=0,e=this.v(),g=this.v(),h=!1,k=!1,c=!1,q=kb(b);Da(q,function(a){if(h||a!=e)if(k||a!=g){var b=d;this.ub||(this.Ja||Xc(this),this.ub=Na(this.Ja));a=parseInt(this.ub[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 ha=a.G;ha&&q.push.apply(q,ha);if(v&&!z("7")){var L=Vc(q);0<L.length&&(q.push.apply(q,
+L),c=!0)}if(!h||!k||ha||c)b.className=q.join(" ");Uc(a,b);return b};f.Oa=function(a){rc(a)&&this.pa(a.a(),!0);a.isEnabled()&&this.na(a,a.s())};var Yc=function(a,b,c){if(a=c||a.da())r(b,"The element passed as a first parameter cannot be null."),wc(b,a)},Uc=function(a,b){r(a);r(b);a.s()||yc(b,"hidden",!a.s());a.isEnabled()||Zc(b,1,!a.isEnabled());a.l&8&&Zc(b,8,!!(a.g&8));a.l&16&&Zc(b,16,!!(a.g&16));a.l&64&&Zc(b,64,!!(a.g&64))};f=O.prototype;f.za=function(a,b){gc(a,!b,!v&&!Wa)};
 f.pa=function(a,b){this.ra(a,this.v()+"-rtl",b)};f.I=function(a){var b;return a.l&32&&(b=a.j())?Cb(b)&&Db(b):!1};f.na=function(a,b){var c;if(a.l&32&&(c=a.j())){if(!b&&a.g&32){try{c.blur()}catch(d){}a.g&32&&a.la(null)}(Cb(c)&&Db(c))!=b&&(b?c.tabIndex=0:(c.tabIndex=-1,c.removeAttribute("tabIndex")))}};f.ja=function(a,b){I(a,b);a&&yc(a,"hidden",!b)};f.t=function(a,b,c){var d=a.a();if(d){var e=$c(this,b);e&&this.ra(a,e,c);Zc(d,b,c)}};
 var Zc=function(a,b,c){Tc||(Tc={1:"disabled",8:"selected",16:"checked",64:"expanded"});if(b=Tc[b])r(a,"The element passed as a first parameter cannot be null."),yc(a,b,c)};O.prototype.j=function(a){return a.a()};O.prototype.v=function(){return"goog-control"};O.prototype.ta=function(a){var b=this.v(),c=[b],d=this.v();d!=b&&c.push(d);b=a.g;for(d=[];b;){var e=b&-b;d.push($c(this,e));b&=~e}c.push.apply(c,d);(a=a.G)&&c.push.apply(c,a);v&&!z("7")&&c.push.apply(c,Vc(c));return c};
-var Vc=function(a,b){var c=[];b&&(a=a.concat([b]));Da([],function(d){!Fa(d,la(t,a))||b&&!t(d,b)||c.push(d.join("_"))});return c},$c=function(a,b){a.Ha||Xc(a);return a.Ha[b]},Xc=function(a){var b=a.v();a.Ha={1:b+"-disabled",2:b+"-hover",4:b+"-active",8:b+"-selected",16:b+"-checked",32:b+"-focused",64:b+"-open"}};var P=function(a,b,c){J.call(this,c);if(!b){b=this.constructor;for(var d;b;){d=ka(b);if(d=Pc[d])break;b=b.e?b.e.constructor:null}b=d?n(d.fa)?d.fa():new d:null}this.b=b;this.Ba=void 0!==a?a:null};p(P,J);f=P.prototype;f.Ba=null;f.g=0;f.l=39;f.cc=255;f.U=0;f.n=!0;f.G=null;f.$=!0;f.xa=!1;f.qb=null;f.ob=function(){return this.$};f.Oa=function(a){this.f&&a!=this.$&&ad(this,a);this.$=a};f.j=function(){return this.b.j(this)};f.ya=function(){return this.ea||(this.ea=new L)};f.zb=function(){return this.b};
-f.ra=function(a,b){b?a&&(this.G?t(this.G,a)||this.G.push(a):this.G=[a],this.b.ra(this,a,!0)):a&&this.G&&Ga(this.G,a)&&(0==this.G.length&&(this.G=null),this.b.ra(this,a,!1))};f.o=function(){var a=this.b.o(this);this.d=a;Yc(this.b,a,this.qb);this.xa||this.b.za(a,!1);this.s()||this.b.ja(a,!1)};f.B=function(){return this.b.B(this.a())};f.Y=function(a){return this.b.Y(a)};f.Ya=function(a){this.d=a=this.b.K(this,a);Yc(this.b,a,this.qb);this.xa||this.b.za(a,!1);this.n="none"!=a.style.display};
-f.D=function(){P.e.D.call(this);this.b.Na(this);if(this.l&-2&&(this.ob()&&ad(this,!0),this.l&32)){var a=this.j();if(a){var b=this.ya();Dc(b,a);mc(this).c(b,"key",this.J).c(a,"focus",this.ma).c(a,"blur",this.la)}}};
-var ad=function(a,b){var c=mc(a),d=a.a();b?(c.c(d,"mouseover",a.Ra).c(d,"mousedown",a.ka).c(d,"mouseup",a.Sa).c(d,"mouseout",a.Qa),a.oa!=aa&&c.c(d,"contextmenu",a.oa),v&&c.c(d,"dblclick",a.sb)):(c.u(d,"mouseover",a.Ra).u(d,"mousedown",a.ka).u(d,"mouseup",a.Sa).u(d,"mouseout",a.Qa),a.oa!=aa&&c.u(d,"contextmenu",a.oa),v&&c.u(d,"dblclick",a.sb))};P.prototype.ca=function(){P.e.ca.call(this);this.ea&&this.ea.detach();this.s()&&this.isEnabled()&&this.b.na(this,!1)};var Wc=function(a,b){a.Ba=b};f=P.prototype;
+var Vc=function(a,b){var c=[];b&&(a=a.concat([b]));Da([],function(d){!Fa(d,la(t,a))||b&&!t(d,b)||c.push(d.join("_"))});return c},$c=function(a,b){a.Ja||Xc(a);return a.Ja[b]},Xc=function(a){var b=a.v();a.Ja={1:b+"-disabled",2:b+"-hover",4:b+"-active",8:b+"-selected",16:b+"-checked",32:b+"-focused",64:b+"-open"}};var P=function(a,b,c){J.call(this,c);if(!b){b=this.constructor;for(var d;b;){d=ka(b);if(d=Pc[d])break;b=b.e?b.e.constructor:null}b=d?n(d.ga)?d.ga():new d:null}this.b=b;this.Ba=void 0!==a?a:null};p(P,J);f=P.prototype;f.Ba=null;f.g=0;f.l=39;f.ec=255;f.T=0;f.n=!0;f.G=null;f.Z=!0;f.xa=!1;f.rb=null;f.pb=function(){return this.Z};f.Pa=function(a){this.f&&a!=this.Z&&ad(this,a);this.Z=a};f.j=function(){return this.b.j(this)};f.ya=function(){return this.ea||(this.ea=new M)};f.Ab=function(){return this.b};
+f.ra=function(a,b){b?a&&(this.G?t(this.G,a)||this.G.push(a):this.G=[a],this.b.ra(this,a,!0)):a&&this.G&&Ga(this.G,a)&&(0==this.G.length&&(this.G=null),this.b.ra(this,a,!1))};f.o=function(){var a=this.b.o(this);this.d=a;Yc(this.b,a,this.rb);this.xa||this.b.za(a,!1);this.s()||this.b.ja(a,!1)};f.B=function(){return this.b.B(this.a())};f.X=function(a){return this.b.X(a)};f.Za=function(a){this.d=a=this.b.K(this,a);Yc(this.b,a,this.rb);this.xa||this.b.za(a,!1);this.n="none"!=a.style.display};
+f.D=function(){P.e.D.call(this);this.b.Oa(this);if(this.l&-2&&(this.pb()&&ad(this,!0),this.l&32)){var a=this.j();if(a){var b=this.ya();Dc(b,a);mc(this).c(b,"key",this.J).c(a,"focus",this.ma).c(a,"blur",this.la)}}};
+var ad=function(a,b){var c=mc(a),d=a.a();b?(c.c(d,"mouseover",a.Sa).c(d,"mousedown",a.ka).c(d,"mouseup",a.Ta).c(d,"mouseout",a.Ra),a.oa!=aa&&c.c(d,"contextmenu",a.oa),v&&c.c(d,"dblclick",a.tb)):(c.u(d,"mouseover",a.Sa).u(d,"mousedown",a.ka).u(d,"mouseup",a.Ta).u(d,"mouseout",a.Ra),a.oa!=aa&&c.u(d,"contextmenu",a.oa),v&&c.u(d,"dblclick",a.tb))};P.prototype.ba=function(){P.e.ba.call(this);this.ea&&this.ea.detach();this.s()&&this.isEnabled()&&this.b.na(this,!1)};var Wc=function(a,b){a.Ba=b};f=P.prototype;
 f.pa=function(a){P.e.pa.call(this,a);var b=this.a();b&&this.b.pa(b,a)};f.za=function(a){this.xa=a;var b=this.a();b&&this.b.za(b,a)};f.s=function(){return this.n};f.ja=function(a,b){if(b||this.n!=a&&this.dispatchEvent(a?"show":"hide")){var c=this.a();c&&this.b.ja(c,a);this.isEnabled()&&this.b.na(this,a);this.n=a;return!0}return!1};f.isEnabled=function(){return!(this.g&1)};
 f.qa=function(a){var b=this.getParent();b&&"function"==typeof b.isEnabled&&!b.isEnabled()||!Q(this,1,!a)||(a||(this.setActive(!1),this.C(!1)),this.s()&&this.b.na(this,a),this.t(1,!a))};f.C=function(a){Q(this,2,a)&&this.t(2,a)};f.setActive=function(a){Q(this,4,a)&&this.t(4,a)};var bd=function(a,b){Q(a,8,b)&&a.t(8,b)},R=function(a,b){Q(a,64,b)&&a.t(64,b)};P.prototype.t=function(a,b){this.l&a&&b!=!!(this.g&a)&&(this.b.t(this,a,b),this.g=b?this.g|a:this.g&~a)};
-var cd=function(a,b,c){if(a.f&&a.g&b&&!c)throw Error("Component already rendered");!c&&a.g&b&&a.t(b,!1);a.l=c?a.l|b:a.l&~b},S=function(a,b){return!!(a.cc&b)&&!!(a.l&b)},Q=function(a,b,c){return!!(a.l&b)&&!!(a.g&b)!=c&&(!(a.U&b)||a.dispatchEvent(jc(b,c)))&&!a.Sb};f=P.prototype;f.Ra=function(a){(!a.relatedTarget||!yb(this.a(),a.relatedTarget))&&this.dispatchEvent("enter")&&this.isEnabled()&&S(this,2)&&this.C(!0)};
-f.Qa=function(a){a.relatedTarget&&yb(this.a(),a.relatedTarget)||!this.dispatchEvent("leave")||(S(this,4)&&this.setActive(!1),S(this,2)&&this.C(!1))};f.oa=aa;f.ka=function(a){this.isEnabled()&&(S(this,2)&&this.C(!0),!Lb(a)||x&&y&&a.ctrlKey||(S(this,4)&&this.setActive(!0),this.b.I(this)&&this.j().focus()));this.xa||!Lb(a)||x&&y&&a.ctrlKey||a.preventDefault()};f.Sa=function(a){this.isEnabled()&&(S(this,2)&&this.C(!0),this.g&4&&dd(this,a)&&S(this,4)&&this.setActive(!1))};
-f.sb=function(a){this.isEnabled()&&dd(this,a)};var dd=function(a,b){if(S(a,16)){var c=!(a.g&16);Q(a,16,c)&&a.t(16,c)}S(a,8)&&bd(a,!0);S(a,64)&&R(a,!(a.g&64));c=new A("action",a);b&&(c.altKey=b.altKey,c.ctrlKey=b.ctrlKey,c.metaKey=b.metaKey,c.shiftKey=b.shiftKey,c.ib=b.ib);return a.dispatchEvent(c)};P.prototype.ma=function(){S(this,32)&&Q(this,32,!0)&&this.t(32,!0)};P.prototype.la=function(){S(this,4)&&this.setActive(!1);S(this,32)&&Q(this,32,!1)&&this.t(32,!1)};
-P.prototype.J=function(a){return this.s()&&this.isEnabled()&&this.lb(a)?(a.preventDefault(),a.stopPropagation(),!0):!1};P.prototype.lb=function(a){return 13==a.keyCode&&dd(this,a)};if(!n(P))throw Error("Invalid component class "+P);if(!n(O))throw Error("Invalid renderer class "+O);var ed=ka(P);Pc[ed]=O;Oc("goog-control",function(){return new P(null)});var T=function(a,b,c){J.call(this,c);this.b=b||N.fa();this.L=a||"vertical"};p(T,J);f=T.prototype;f.ub=null;f.ea=null;f.b=null;f.L=null;f.n=!0;f.V=!0;f.Za=!0;f.h=-1;f.i=null;f.ba=!1;f.Pb=!1;f.Ob=!0;f.M=null;f.j=function(){return this.ub||this.b.j(this)};f.ya=function(){return this.ea||(this.ea=new L(this.j()))};f.zb=function(){return this.b};f.o=function(){this.d=this.b.o(this)};f.B=function(){return this.b.B(this.a())};f.Y=function(a){return this.b.Y(a)};
-f.Ya=function(a){this.d=this.b.K(this,a);"none"==a.style.display&&(this.n=!1)};f.D=function(){T.e.D.call(this);pc(this,function(a){a.f&&fd(this,a)},this);var a=this.a();this.b.Na(this);this.ja(this.n,!0);mc(this).c(this,"enter",this.Ib).c(this,"highlight",this.Jb).c(this,"unhighlight",this.Lb).c(this,"open",this.Kb).c(this,"close",this.Gb).c(a,"mousedown",this.ka).c(pb(a),"mouseup",this.Hb).c(a,["mousedown","mouseup","mouseover","mouseout","contextmenu"],this.Fb);this.I()&&gd(this,!0)};
-var gd=function(a,b){var c=mc(a),d=a.j();b?c.c(d,"focus",a.ma).c(d,"blur",a.la).c(a.ya(),"key",a.J):c.u(d,"focus",a.ma).u(d,"blur",a.la).u(a.ya(),"key",a.J)};f=T.prototype;f.ca=function(){hd(this,-1);this.i&&R(this.i,!1);this.ba=!1;T.e.ca.call(this)};f.Ib=function(){return!0};
-f.Jb=function(a){var b=sc(this,a.target);if(-1<b&&b!=this.h){var c=K(this,this.h);c&&c.C(!1);this.h=b;c=K(this,this.h);this.ba&&c.setActive(!0);this.Ob&&this.i&&c!=this.i&&(c.l&64?R(c,!0):R(this.i,!1))}b=this.a();r(b,"The DOM element for the container cannot be null.");null!=a.target.a()&&yc(b,"activedescendant",a.target.a().id)};f.Lb=function(a){a.target==K(this,this.h)&&(this.h=-1);a=this.a();r(a,"The DOM element for the container cannot be null.");a.removeAttribute(xc("activedescendant"))};
-f.Kb=function(a){(a=a.target)&&a!=this.i&&a.getParent()==this&&(this.i&&R(this.i,!1),this.i=a)};f.Gb=function(a){a.target==this.i&&(this.i=null)};f.ka=function(a){this.V&&(this.ba=!0);var b=this.j();b&&Cb(b)&&Db(b)?b.focus():a.preventDefault()};f.Hb=function(){this.ba=!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.Sa(a);break;case "mouseover":b.Ra(a);break;case "mouseout":b.Qa(a);break;case "contextmenu":b.oa(a)}};f.ma=function(){};f.la=function(){hd(this,-1);this.ba=!1;this.i&&R(this.i,!1)};
-f.J=function(a){return this.isEnabled()&&this.s()&&(0!=qc(this)||this.ub)&&this.lb(a)?(a.preventDefault(),a.stopPropagation(),!0):!1};
-f.lb=function(a){var b=K(this,this.h);if(b&&"function"==typeof b.J&&b.J(a)||this.i&&this.i!=b&&"function"==typeof this.i.J&&this.i.J(a))return!0;if(a.shiftKey||a.ctrlKey||a.metaKey||a.altKey)return!1;switch(a.keyCode){case 27:if(this.I())this.j().blur();else return!1;break;case 36:id(this);break;case 35:jd(this);break;case 38:if("vertical"==this.L)kd(this);else return!1;break;case 37:if("horizontal"==this.L)rc(this)?ld(this):kd(this);else return!1;break;case 40:if("vertical"==this.L)ld(this);else return!1;
-break;case 39:if("horizontal"==this.L)rc(this)?kd(this):ld(this);else return!1;break;default:return!1}return!0};var fd=function(a,b){var c=b.a(),c=c.id||(c.id=kc(b));a.M||(a.M={});a.M[c]=b};T.prototype.Ca=function(a,b){Ba(a,P,"The child of a container must be a control");T.e.Ca.call(this,a,b)};T.prototype.Ua=function(a,b,c){a.U|=2;a.U|=64;!this.I()&&this.Pb||cd(a,32,!1);a.Oa(!1);T.e.Ua.call(this,a,b,c);a.f&&this.f&&fd(this,a);b<=this.h&&this.h++};
-T.prototype.removeChild=function(a,b){if(a=m(a)?nc(this,a):a){var c=sc(this,a);-1!=c&&(c==this.h?(a.C(!1),this.h=-1):c<this.h&&this.h--);var d=a.a();d&&d.id&&this.M&&(c=this.M,d=d.id,d in c&&delete c[d])}a=T.e.removeChild.call(this,a,b);a.Oa(!0);return a};var Sc=function(a,b){if(a.a())throw Error("Component already rendered");a.L=b};f=T.prototype;f.s=function(){return this.n};
-f.ja=function(a,b){if(b||this.n!=a&&this.dispatchEvent(a?"show":"hide")){this.n=a;var c=this.a();c&&(I(c,a),this.I()&&Qc(this.j(),this.V&&this.n),b||this.dispatchEvent(this.n?"aftershow":"afterhide"));return!0}return!1};f.isEnabled=function(){return this.V};f.qa=function(a){this.V!=a&&this.dispatchEvent(a?"enable":"disable")&&(a?(this.V=!0,pc(this,function(a){a.vb?delete a.vb:a.qa(!0)})):(pc(this,function(a){a.isEnabled()?a.qa(!1):a.vb=!0}),this.ba=this.V=!1),this.I()&&Qc(this.j(),a&&this.n))};
-f.I=function(){return this.Za};f.na=function(a){a!=this.Za&&this.f&&gd(this,a);this.Za=a;this.V&&this.n&&Qc(this.j(),a)};var hd=function(a,b){var c=K(a,b);c?c.C(!0):-1<a.h&&K(a,a.h).C(!1)};T.prototype.C=function(a){hd(this,sc(this,a))};
-var id=function(a){md(a,function(a,c){return(a+1)%c},qc(a)-1)},jd=function(a){md(a,function(a,c){a--;return 0>a?c-1:a},0)},ld=function(a){md(a,function(a,c){return(a+1)%c},a.h)},kd=function(a){md(a,function(a,c){a--;return 0>a?c-1:a},a.h)},md=function(a,b,c){c=0>c?sc(a,a.i):c;var d=qc(a);c=b.call(a,c,d);for(var e=0;e<=d;){var g=K(a,c);if(g&&g.s()&&g.isEnabled()&&g.l&2){a.Va(c);break}e++;c=b.call(a,c,d)}};T.prototype.Va=function(a){hd(this,a)};var U=function(){};p(U,O);ba(U);f=U.prototype;f.v=function(){return"goog-tab"};f.T=function(){return"tab"};f.o=function(a){var b=U.e.o.call(this,a);(a=a.Ta())&&this.Wa(b,a);return b};f.K=function(a,b){b=U.e.K.call(this,a,b);var c=this.Ta(b);c&&(a.rb=c);a.g&8&&(c=a.getParent())&&n(c.W)&&(a.t(8,!1),c.W(a));return b};f.Ta=function(a){return a.title||""};f.Wa=function(a,b){a&&(a.title=b||"")};var nd=function(a,b,c){P.call(this,a,b||U.fa(),c);cd(this,8,!0);this.U|=9};p(nd,P);nd.prototype.Ta=function(){return this.rb};nd.prototype.Wa=function(a){this.zb().Wa(this.a(),a);this.rb=a};Oc("goog-tab",function(){return new nd(null)});var V=function(){};p(V,N);ba(V);V.prototype.v=function(){return"goog-tab-bar"};V.prototype.T=function(){return"tablist"};V.prototype.ab=function(a,b,c){this.Ab||(this.Ja||od(this),this.Ab=Na(this.Ja));var d=this.Ab[b];d?(Sc(a,pd(d)),a.wb=d):V.e.ab.call(this,a,b,c)};V.prototype.ta=function(a){var b=V.e.ta.call(this,a);this.Ja||od(this);b.push(this.Ja[a.wb]);return b};var od=function(a){var b=a.v();a.Ja={top:b+"-top",bottom:b+"-bottom",start:b+"-start",end:b+"-end"}};var W=function(a,b,c){a=a||"top";Sc(this,pd(a));this.wb=a;T.call(this,this.L,b||V.fa(),c);qd(this)};p(W,T);f=W.prototype;f.Zb=!0;f.H=null;f.D=function(){W.e.D.call(this);qd(this)};f.removeChild=function(a,b){rd(this,a);return W.e.removeChild.call(this,a,b)};f.Va=function(a){W.e.Va.call(this,a);this.Zb&&this.W(K(this,a))};f.W=function(a){a?bd(a,!0):this.H&&bd(this.H,!1)};
-var rd=function(a,b){if(b&&b==a.H){for(var c=sc(a,b),d=c-1;b=K(a,d);d--)if(b.s()&&b.isEnabled()){a.W(b);return}for(c+=1;b=K(a,c);c++)if(b.s()&&b.isEnabled()){a.W(b);return}a.W(null)}};f=W.prototype;f.Xb=function(a){this.H&&this.H!=a.target&&bd(this.H,!1);this.H=a.target};f.Yb=function(a){a.target==this.H&&(this.H=null)};f.Vb=function(a){rd(this,a.target)};f.Wb=function(a){rd(this,a.target)};f.ma=function(){K(this,this.h)||this.C(this.H||K(this,0))};
-var qd=function(a){mc(a).c(a,"select",a.Xb).c(a,"unselect",a.Yb).c(a,"disable",a.Vb).c(a,"hide",a.Wb)},pd=function(a){return"start"==a||"end"==a?"vertical":"horizontal"};Oc("goog-tab-bar",function(){return new W});var X=function(a,b,c,d,e){function g(a){a&&(a.tabIndex=0,wc(a,h.T()),Lc(a,"goog-zippy-header"),sd(h,a),a&&h.Mb.c(a,"keydown",h.Nb))}H.call(this);this.A=e||qb();this.R=this.A.a(a)||null;this.Aa=this.A.a(d||null);this.da=(this.Pa=n(b)?b:null)||!b?null:this.A.a(b);this.k=!0==c;this.Mb=new G(this);this.pb=new G(this);var h=this;g(this.R);g(this.Aa);this.S(this.k)};p(X,H);f=X.prototype;f.$=!0;f.T=function(){return"tab"};f.B=function(){return this.da};f.toggle=function(){this.S(!this.k)};
-f.S=function(a){this.da?I(this.da,a):a&&this.Pa&&(this.da=this.Pa());this.da&&Lc(this.da,"goog-zippy-content");if(this.Aa)I(this.R,!a),I(this.Aa,a);else if(this.R){var b=this.R;a?Lc(b,"goog-zippy-expanded"):Mc(b,"goog-zippy-expanded");b=this.R;a?Mc(b,"goog-zippy-collapsed"):Lc(b,"goog-zippy-collapsed");yc(this.R,"expanded",a)}this.k=a;this.dispatchEvent(new td("toggle",this))};f.ob=function(){return this.$};f.Oa=function(a){this.$!=a&&((this.$=a)?(sd(this,this.R),sd(this,this.Aa)):this.pb.$a())};
-var sd=function(a,b){b&&a.pb.c(b,"click",a.$b)};X.prototype.Nb=function(a){if(13==a.keyCode||32==a.keyCode)this.toggle(),this.dispatchEvent(new A("action",this)),a.preventDefault(),a.stopPropagation()};X.prototype.$b=function(){this.toggle();this.dispatchEvent(new A("action",this))};var td=function(a,b){A.call(this,a,b)};p(td,A);var Z=function(a,b){this.nb=[];for(var c=sb("span","ae-zippy",rb(document,a)),d=0,e;e=c[d];d++){var g=e.parentNode.parentNode.parentNode;if(void 0!=g.nextElementSibling)g=g.nextElementSibling;else for(g=g.nextSibling;g&&1!=g.nodeType;)g=g.nextSibling;e=new X(e,g,!1);this.nb.push(e)}this.fc=new Y(this.nb,rb(document,b))};Z.prototype.ic=function(){return this.fc};Z.prototype.jc=function(){return this.nb};
-var Y=function(a,b){this.ua=a;if(this.ua.length)for(var c=0,d;d=this.ua[c];c++)E(d,"toggle",this.Ub,!1,this);this.Ka=0;this.k=!1;c="ae-toggle ae-plus ae-action";this.ua.length||(c+=" ae-disabled");this.P=wb("span",{className:c},"Expand All");E(this.P,"click",this.Tb,!1,this);b&&b.appendChild(this.P)};Y.prototype.Tb=function(){this.ua.length&&this.S(!this.k)};
-Y.prototype.Ub=function(a){a=a.currentTarget;this.Ka=a.k?this.Ka+1:this.Ka-1;a.k!=this.k&&(a.k?(this.k=!0,ud(this,!0)):0==this.Ka&&(this.k=!1,ud(this,!1)))};Y.prototype.S=function(a){this.k=a;a=0;for(var b;b=this.ua[a];a++)b.k!=this.k&&b.S(this.k);ud(this)};
-var ud=function(a,b){(void 0!==b?b:a.k)?(nb(a.P,"ae-plus"),lb(a.P,"ae-minus"),zb(a.P,"Collapse All")):(nb(a.P,"ae-minus"),lb(a.P,"ae-plus"),zb(a.P,"Expand All"))},vd=function(a){this.ac=a;this.Cb={};var b,c=wb("div",{},b=wb("div",{id:"ae-stats-details-tabs",className:"goog-tab-bar goog-tab-bar-top"}),wb("div",{className:"goog-tab-bar-clear"}),a=wb("div",{id:"ae-stats-details-tabs-content",className:"goog-tab-content"})),d=new W;d.K(b);E(d,"select",this.Bb,!1,this);E(d,"unselect",this.Bb,!1,this);
-b=0;for(var e;e=this.ac[b];b++)if(e=rb(document,"ae-stats-details-"+e)){var g=sb("h2",null,e)[0],h;h=g;var k=void 0;jb&&"innerText"in h?k=h.innerText.replace(/(\r\n|\r|\n)/g,"\n"):(k=[],Eb(h,k,!0),k=k.join(""));k=k.replace(/ \xAD /g," ").replace(/\xAD/g,"");k=k.replace(/\u200B/g,"");jb||(k=k.replace(/ +/g," "));" "!=k&&(k=k.replace(/^\s*/,""));h=k;g&&g.parentNode&&g.parentNode.removeChild(g);g=new nd(h);this.Cb[ka(g)]=e;d.Ca(g,!0);a.appendChild(e);0==b?d.W(g):I(e,!1)}rb(document,"bd").appendChild(c)};
-vd.prototype.Bb=function(a){var b=this.Cb[ka(a.target)];I(b,"select"==a.type)};ma("ae.Stats.Details.Tabs",vd);ma("goog.ui.Zippy",X);X.prototype.setExpanded=X.prototype.S;ma("ae.Stats.MakeZippys",Z);Z.prototype.getExpandCollapse=Z.prototype.ic;Z.prototype.getZippys=Z.prototype.jc;Y.prototype.setExpanded=Y.prototype.S;var $=function(){this.bb=[];this.hb=[]},wd=[[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]],xd=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<wd.length;c++)if(a<=wd[c][2])return[wd[c][0],wd[c][1]*b,wd[c][2]*b];return[5,2*b,10*b]};$.prototype.gb="stats/static/pix.gif";$.prototype.w="ae-stats-gantt-";$.prototype.eb=0;$.prototype.write=function(a){this.hb.push(a)};
-var yd=function(a,b,c,d){a.write('<tr class="'+a.w+'axisrow"><td width="20%"></td><td>');a.write('<div class="'+a.w+'axis">');for(var e=0;e<=b;e++)a.write('<img class="'+a.w+'tick" src="'+a.gb+'" alt="" '),a.write('style="left:'+e*c*d+'%"\n>'),a.write('<span class="'+a.w+'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=xd(this.eb),b=a[0],c=a[1],a=100/a[2];this.write('<table class="'+this.w+'table">\n');yd(this,b,c,a);for(var d=0;d<this.bb.length;d++){var e=this.bb[d];this.write('<tr class="'+this.w+'datarow"><td width="20%">');0<e.label.length&&(0<e.ia.length&&this.write('<a class="'+this.w+'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.w+'container">');0<e.ia.length&&this.write('<a class="'+
-this.w+'link" href="'+e.ia+'"\n>');this.write('<img class="'+this.w+'bar" src="'+this.gb+'" alt="" ');this.write('style="left:'+e.start*a+"%;width:"+e.duration*a+'%;min-width:1px"\n>');0<e.cb&&(this.write('<img class="'+this.w+'extra" src="'+this.gb+'" alt="" '),this.write('style="left:'+e.start*a+"%;width:"+e.cb*a+'%"\n>'));0<e.yb.length&&(this.write('<span class="'+this.w+'inline" style="left:'+(e.start+Math.max(e.duration,e.cb))*a+'%">&nbsp;'),this.write(e.yb),this.write("</span>"));0<e.ia.length&&
-this.write("</a>");this.write("</div></td></tr>\n")}yd(this,b,c,a);this.write("</table>\n");return this.hb.join("")};$.prototype.gc=function(a,b,c,d,e,g){this.eb=Math.max(this.eb,Math.max(b+c,b+d));this.bb.push({label:a,start:b,duration:c,cb:d,yb:e,ia:g})};ma("Gantt",$);$.prototype.add_bar=$.prototype.gc;$.prototype.draw=$.prototype.hc;})();
+var cd=function(a,b,c){if(a.f&&a.g&b&&!c)throw Error("Component already rendered");!c&&a.g&b&&a.t(b,!1);a.l=c?a.l|b:a.l&~b},S=function(a,b){return!!(a.ec&b)&&!!(a.l&b)},Q=function(a,b,c){return!!(a.l&b)&&!!(a.g&b)!=c&&(!(a.T&b)||a.dispatchEvent(jc(b,c)))&&!a.Ub};f=P.prototype;f.Sa=function(a){(!a.relatedTarget||!yb(this.a(),a.relatedTarget))&&this.dispatchEvent("enter")&&this.isEnabled()&&S(this,2)&&this.C(!0)};
+f.Ra=function(a){a.relatedTarget&&yb(this.a(),a.relatedTarget)||!this.dispatchEvent("leave")||(S(this,4)&&this.setActive(!1),S(this,2)&&this.C(!1))};f.oa=aa;f.ka=function(a){this.isEnabled()&&(S(this,2)&&this.C(!0),!Lb(a)||x&&y&&a.ctrlKey||(S(this,4)&&this.setActive(!0),this.b.I(this)&&this.j().focus()));this.xa||!Lb(a)||x&&y&&a.ctrlKey||a.preventDefault()};f.Ta=function(a){this.isEnabled()&&(S(this,2)&&this.C(!0),this.g&4&&dd(this,a)&&S(this,4)&&this.setActive(!1))};
+f.tb=function(a){this.isEnabled()&&dd(this,a)};var dd=function(a,b){if(S(a,16)){var c=!(a.g&16);Q(a,16,c)&&a.t(16,c)}S(a,8)&&bd(a,!0);S(a,64)&&R(a,!(a.g&64));c=new A("action",a);b&&(c.altKey=b.altKey,c.ctrlKey=b.ctrlKey,c.metaKey=b.metaKey,c.shiftKey=b.shiftKey,c.jb=b.jb);return a.dispatchEvent(c)};P.prototype.ma=function(){S(this,32)&&Q(this,32,!0)&&this.t(32,!0)};P.prototype.la=function(){S(this,4)&&this.setActive(!1);S(this,32)&&Q(this,32,!1)&&this.t(32,!1)};
+P.prototype.J=function(a){return this.s()&&this.isEnabled()&&this.mb(a)?(a.preventDefault(),a.stopPropagation(),!0):!1};P.prototype.mb=function(a){return 13==a.keyCode&&dd(this,a)};if(!n(P))throw Error("Invalid component class "+P);if(!n(O))throw Error("Invalid renderer class "+O);var ed=ka(P);Pc[ed]=O;Oc("goog-control",function(){return new P(null)});var T=function(a,b,c){J.call(this,c);this.b=b||N.ga();this.L=a||"vertical"};p(T,J);f=T.prototype;f.vb=null;f.ea=null;f.b=null;f.L=null;f.n=!0;f.U=!0;f.$a=!0;f.h=-1;f.i=null;f.aa=!1;f.Rb=!1;f.Qb=!0;f.M=null;f.j=function(){return this.vb||this.b.j(this)};f.ya=function(){return this.ea||(this.ea=new M(this.j()))};f.Ab=function(){return this.b};f.o=function(){this.d=this.b.o(this)};f.B=function(){return this.b.B(this.a())};f.X=function(a){return this.b.X(a)};
+f.Za=function(a){this.d=this.b.K(this,a);"none"==a.style.display&&(this.n=!1)};f.D=function(){T.e.D.call(this);pc(this,function(a){a.f&&fd(this,a)},this);var a=this.a();this.b.Oa(this);this.ja(this.n,!0);mc(this).c(this,"enter",this.Kb).c(this,"highlight",this.Lb).c(this,"unhighlight",this.Nb).c(this,"open",this.Mb).c(this,"close",this.Ib).c(a,"mousedown",this.ka).c(pb(a),"mouseup",this.Jb).c(a,["mousedown","mouseup","mouseover","mouseout","contextmenu"],this.Hb);this.I()&&gd(this,!0)};
+var gd=function(a,b){var c=mc(a),d=a.j();b?c.c(d,"focus",a.ma).c(d,"blur",a.la).c(a.ya(),"key",a.J):c.u(d,"focus",a.ma).u(d,"blur",a.la).u(a.ya(),"key",a.J)};f=T.prototype;f.ba=function(){hd(this,-1);this.i&&R(this.i,!1);this.aa=!1;T.e.ba.call(this)};f.Kb=function(){return!0};
+f.Lb=function(a){var b=sc(this,a.target);if(-1<b&&b!=this.h){var c=K(this,this.h);c&&c.C(!1);this.h=b;c=K(this,this.h);this.aa&&c.setActive(!0);this.Qb&&this.i&&c!=this.i&&(c.l&64?R(c,!0):R(this.i,!1))}b=this.a();r(b,"The DOM element for the container cannot be null.");null!=a.target.a()&&yc(b,"activedescendant",a.target.a().id)};f.Nb=function(a){a.target==K(this,this.h)&&(this.h=-1);a=this.a();r(a,"The DOM element for the container cannot be null.");a.removeAttribute(xc("activedescendant"))};
+f.Mb=function(a){(a=a.target)&&a!=this.i&&a.getParent()==this&&(this.i&&R(this.i,!1),this.i=a)};f.Ib=function(a){a.target==this.i&&(this.i=null)};f.ka=function(a){this.U&&(this.aa=!0);var b=this.j();b&&Cb(b)&&Db(b)?b.focus():a.preventDefault()};f.Jb=function(){this.aa=!1};
+f.Hb=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.Ta(a);break;case "mouseover":b.Sa(a);break;case "mouseout":b.Ra(a);break;case "contextmenu":b.oa(a)}};f.ma=function(){};f.la=function(){hd(this,-1);this.aa=!1;this.i&&R(this.i,!1)};
+f.J=function(a){return this.isEnabled()&&this.s()&&(0!=qc(this)||this.vb)&&this.mb(a)?(a.preventDefault(),a.stopPropagation(),!0):!1};
+f.mb=function(a){var b=K(this,this.h);if(b&&"function"==typeof b.J&&b.J(a)||this.i&&this.i!=b&&"function"==typeof this.i.J&&this.i.J(a))return!0;if(a.shiftKey||a.ctrlKey||a.metaKey||a.altKey)return!1;switch(a.keyCode){case 27:if(this.I())this.j().blur();else return!1;break;case 36:id(this);break;case 35:jd(this);break;case 38:if("vertical"==this.L)kd(this);else return!1;break;case 37:if("horizontal"==this.L)rc(this)?ld(this):kd(this);else return!1;break;case 40:if("vertical"==this.L)ld(this);else return!1;
+break;case 39:if("horizontal"==this.L)rc(this)?kd(this):ld(this);else return!1;break;default:return!1}return!0};var fd=function(a,b){var c=b.a(),c=c.id||(c.id=kc(b));a.M||(a.M={});a.M[c]=b};T.prototype.Ca=function(a,b){Ba(a,P,"The child of a container must be a control");T.e.Ca.call(this,a,b)};T.prototype.Va=function(a,b,c){a.T|=2;a.T|=64;!this.I()&&this.Rb||cd(a,32,!1);a.Pa(!1);T.e.Va.call(this,a,b,c);a.f&&this.f&&fd(this,a);b<=this.h&&this.h++};
+T.prototype.removeChild=function(a,b){if(a=m(a)?nc(this,a):a){var c=sc(this,a);-1!=c&&(c==this.h?(a.C(!1),this.h=-1):c<this.h&&this.h--);var d=a.a();d&&d.id&&this.M&&(c=this.M,d=d.id,d in c&&delete c[d])}a=T.e.removeChild.call(this,a,b);a.Pa(!0);return a};var Sc=function(a,b){if(a.a())throw Error("Component already rendered");a.L=b};f=T.prototype;f.s=function(){return this.n};
+f.ja=function(a,b){if(b||this.n!=a&&this.dispatchEvent(a?"show":"hide")){this.n=a;var c=this.a();c&&(I(c,a),this.I()&&Qc(this.j(),this.U&&this.n),b||this.dispatchEvent(this.n?"aftershow":"afterhide"));return!0}return!1};f.isEnabled=function(){return this.U};f.qa=function(a){this.U!=a&&this.dispatchEvent(a?"enable":"disable")&&(a?(this.U=!0,pc(this,function(a){a.xb?delete a.xb:a.qa(!0)})):(pc(this,function(a){a.isEnabled()?a.qa(!1):a.xb=!0}),this.aa=this.U=!1),this.I()&&Qc(this.j(),a&&this.n))};
+f.I=function(){return this.$a};f.na=function(a){a!=this.$a&&this.f&&gd(this,a);this.$a=a;this.U&&this.n&&Qc(this.j(),a)};var hd=function(a,b){var c=K(a,b);c?c.C(!0):-1<a.h&&K(a,a.h).C(!1)};T.prototype.C=function(a){hd(this,sc(this,a))};
+var id=function(a){md(a,function(a,c){return(a+1)%c},qc(a)-1)},jd=function(a){md(a,function(a,c){a--;return 0>a?c-1:a},0)},ld=function(a){md(a,function(a,c){return(a+1)%c},a.h)},kd=function(a){md(a,function(a,c){a--;return 0>a?c-1:a},a.h)},md=function(a,b,c){c=0>c?sc(a,a.i):c;var d=qc(a);c=b.call(a,c,d);for(var e=0;e<=d;){var g=K(a,c);if(g&&g.s()&&g.isEnabled()&&g.l&2){a.Wa(c);break}e++;c=b.call(a,c,d)}};T.prototype.Wa=function(a){hd(this,a)};var U=function(){};p(U,O);ba(U);f=U.prototype;f.v=function(){return"goog-tab"};f.da=function(){return"tab"};f.o=function(a){var b=U.e.o.call(this,a);(a=a.Ua())&&this.Xa(b,a);return b};f.K=function(a,b){b=U.e.K.call(this,a,b);var c=this.Ua(b);c&&(a.sb=c);a.g&8&&(c=a.getParent())&&n(c.V)&&(a.t(8,!1),c.V(a));return b};f.Ua=function(a){return a.title||""};f.Xa=function(a,b){a&&(a.title=b||"")};var nd=function(a,b,c){P.call(this,a,b||U.ga(),c);cd(this,8,!0);this.T|=9};p(nd,P);nd.prototype.Ua=function(){return this.sb};nd.prototype.Xa=function(a){this.Ab().Xa(this.a(),a);this.sb=a};Oc("goog-tab",function(){return new nd(null)});var V=function(){this.Gb="tablist"};p(V,N);ba(V);V.prototype.v=function(){return"goog-tab-bar"};V.prototype.bb=function(a,b,c){this.Bb||(this.Ka||od(this),this.Bb=Na(this.Ka));var d=this.Bb[b];d?(Sc(a,pd(d)),a.yb=d):V.e.bb.call(this,a,b,c)};V.prototype.ta=function(a){var b=V.e.ta.call(this,a);this.Ka||od(this);b.push(this.Ka[a.yb]);return b};var od=function(a){var b=a.v();a.Ka={top:b+"-top",bottom:b+"-bottom",start:b+"-start",end:b+"-end"}};var W=function(a,b,c){a=a||"top";Sc(this,pd(a));this.yb=a;T.call(this,this.L,b||V.ga(),c);qd(this)};p(W,T);f=W.prototype;f.ac=!0;f.H=null;f.D=function(){W.e.D.call(this);qd(this)};f.removeChild=function(a,b){rd(this,a);return W.e.removeChild.call(this,a,b)};f.Wa=function(a){W.e.Wa.call(this,a);this.ac&&this.V(K(this,a))};f.V=function(a){a?bd(a,!0):this.H&&bd(this.H,!1)};
+var rd=function(a,b){if(b&&b==a.H){for(var c=sc(a,b),d=c-1;b=K(a,d);d--)if(b.s()&&b.isEnabled()){a.V(b);return}for(c+=1;b=K(a,c);c++)if(b.s()&&b.isEnabled()){a.V(b);return}a.V(null)}};f=W.prototype;f.Zb=function(a){this.H&&this.H!=a.target&&bd(this.H,!1);this.H=a.target};f.$b=function(a){a.target==this.H&&(this.H=null)};f.Xb=function(a){rd(this,a.target)};f.Yb=function(a){rd(this,a.target)};f.ma=function(){K(this,this.h)||this.C(this.H||K(this,0))};
+var qd=function(a){mc(a).c(a,"select",a.Zb).c(a,"unselect",a.$b).c(a,"disable",a.Xb).c(a,"hide",a.Yb)},pd=function(a){return"start"==a||"end"==a?"vertical":"horizontal"};Oc("goog-tab-bar",function(){return new W});var X=function(a,b,c,d,e){function g(a){a&&(a.tabIndex=0,wc(a,h.da()),Lc(a,"goog-zippy-header"),sd(h,a),a&&h.Ob.c(a,"keydown",h.Pb))}H.call(this);this.A=e||qb();this.R=this.A.a(a)||null;this.Aa=this.A.a(d||null);this.ca=(this.Qa=n(b)?b:null)||!b?null:this.A.a(b);this.k=!0==c;this.Ob=new G(this);this.qb=new G(this);var h=this;g(this.R);g(this.Aa);this.S(this.k)};p(X,H);f=X.prototype;f.Z=!0;f.da=function(){return"tab"};f.B=function(){return this.ca};f.toggle=function(){this.S(!this.k)};
+f.S=function(a){this.ca?I(this.ca,a):a&&this.Qa&&(this.ca=this.Qa());this.ca&&Lc(this.ca,"goog-zippy-content");if(this.Aa)I(this.R,!a),I(this.Aa,a);else if(this.R){var b=this.R;a?Lc(b,"goog-zippy-expanded"):Mc(b,"goog-zippy-expanded");b=this.R;a?Mc(b,"goog-zippy-collapsed"):Lc(b,"goog-zippy-collapsed");yc(this.R,"expanded",a)}this.k=a;this.dispatchEvent(new td("toggle",this))};f.pb=function(){return this.Z};f.Pa=function(a){this.Z!=a&&((this.Z=a)?(sd(this,this.R),sd(this,this.Aa)):this.qb.ab())};
+var sd=function(a,b){b&&a.qb.c(b,"click",a.bc)};X.prototype.Pb=function(a){if(13==a.keyCode||32==a.keyCode)this.toggle(),this.dispatchEvent(new A("action",this)),a.preventDefault(),a.stopPropagation()};X.prototype.bc=function(){this.toggle();this.dispatchEvent(new A("action",this))};var td=function(a,b){A.call(this,a,b)};p(td,A);var Z=function(a,b){this.ob=[];for(var c=sb("span","ae-zippy",rb(document,a)),d=0,e;e=c[d];d++){var g=e.parentNode.parentNode.parentNode;if(void 0!=g.nextElementSibling)g=g.nextElementSibling;else for(g=g.nextSibling;g&&1!=g.nodeType;)g=g.nextSibling;e=new X(e,g,!1);this.ob.push(e)}this.hc=new Y(this.ob,rb(document,b))};Z.prototype.kc=function(){return this.hc};Z.prototype.lc=function(){return this.ob};
+var Y=function(a,b){this.va=a;if(this.va.length)for(var c=0,d;d=this.va[c];c++)E(d,"toggle",this.Wb,!1,this);this.La=0;this.k=!1;c="ae-toggle ae-plus ae-action";this.va.length||(c+=" ae-disabled");this.P=wb("span",{className:c},"Expand All");E(this.P,"click",this.Vb,!1,this);b&&b.appendChild(this.P)};Y.prototype.Vb=function(){this.va.length&&this.S(!this.k)};
+Y.prototype.Wb=function(a){a=a.currentTarget;this.La=a.k?this.La+1:this.La-1;a.k!=this.k&&(a.k?(this.k=!0,ud(this,!0)):0==this.La&&(this.k=!1,ud(this,!1)))};Y.prototype.S=function(a){this.k=a;a=0;for(var b;b=this.va[a];a++)b.k!=this.k&&b.S(this.k);ud(this)};
+var ud=function(a,b){(void 0!==b?b:a.k)?(nb(a.P,"ae-plus"),lb(a.P,"ae-minus"),zb(a.P,"Collapse All")):(nb(a.P,"ae-minus"),lb(a.P,"ae-plus"),zb(a.P,"Expand All"))},vd=function(a){this.cc=a;this.Db={};var b,c=wb("div",{},b=wb("div",{id:"ae-stats-details-tabs",className:"goog-tab-bar goog-tab-bar-top"}),wb("div",{className:"goog-tab-bar-clear"}),a=wb("div",{id:"ae-stats-details-tabs-content",className:"goog-tab-content"})),d=new W;d.K(b);E(d,"select",this.Cb,!1,this);E(d,"unselect",this.Cb,!1,this);
+b=0;for(var e;e=this.cc[b];b++)if(e=rb(document,"ae-stats-details-"+e)){var g=sb("h2",null,e)[0],h;h=g;var k=void 0;jb&&"innerText"in h?k=h.innerText.replace(/(\r\n|\r|\n)/g,"\n"):(k=[],Eb(h,k,!0),k=k.join(""));k=k.replace(/ \xAD /g," ").replace(/\xAD/g,"");k=k.replace(/\u200B/g,"");jb||(k=k.replace(/ +/g," "));" "!=k&&(k=k.replace(/^\s*/,""));h=k;g&&g.parentNode&&g.parentNode.removeChild(g);g=new nd(h);this.Db[ka(g)]=e;d.Ca(g,!0);a.appendChild(e);0==b?d.V(g):I(e,!1)}rb(document,"bd").appendChild(c)};
+vd.prototype.Cb=function(a){var b=this.Db[ka(a.target)];I(b,"select"==a.type)};ma("ae.Stats.Details.Tabs",vd);ma("goog.ui.Zippy",X);X.prototype.setExpanded=X.prototype.S;ma("ae.Stats.MakeZippys",Z);Z.prototype.getExpandCollapse=Z.prototype.kc;Z.prototype.getZippys=Z.prototype.lc;Y.prototype.setExpanded=Y.prototype.S;var $=function(){this.cb=[];this.ib=[]},wd=[[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]],xd=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<wd.length;c++)if(a<=wd[c][2])return[wd[c][0],wd[c][1]*b,wd[c][2]*b];return[5,2*b,10*b]};$.prototype.hb="stats/static/pix.gif";$.prototype.w="ae-stats-gantt-";$.prototype.fb=0;$.prototype.write=function(a){this.ib.push(a)};
+var yd=function(a,b,c,d){a.write('<tr class="'+a.w+'axisrow"><td width="20%"></td><td>');a.write('<div class="'+a.w+'axis">');for(var e=0;e<=b;e++)a.write('<img class="'+a.w+'tick" src="'+a.hb+'" alt="" '),a.write('style="left:'+e*c*d+'%"\n>'),a.write('<span class="'+a.w+'scale" style="left:'+e*c*d+'%">'),a.write("&nbsp;"+e*c+"</span>");a.write("</div></td></tr>\n")};
+$.prototype.jc=function(){this.ib=[];var a=xd(this.fb),b=a[0],c=a[1],a=100/a[2];this.write('<table class="'+this.w+'table">\n');yd(this,b,c,a);for(var d=0;d<this.cb.length;d++){var e=this.cb[d];this.write('<tr class="'+this.w+'datarow"><td width="20%">');0<e.label.length&&(0<e.ia.length&&this.write('<a class="'+this.w+'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.w+'container">');0<e.ia.length&&this.write('<a class="'+
+this.w+'link" href="'+e.ia+'"\n>');this.write('<img class="'+this.w+'bar" src="'+this.hb+'" alt="" ');this.write('style="left:'+e.start*a+"%;width:"+e.duration*a+'%;min-width:1px"\n>');0<e.eb&&(this.write('<img class="'+this.w+'extra" src="'+this.hb+'" alt="" '),this.write('style="left:'+e.start*a+"%;width:"+e.eb*a+'%"\n>'));0<e.zb.length&&(this.write('<span class="'+this.w+'inline" style="left:'+(e.start+Math.max(e.duration,e.eb))*a+'%">&nbsp;'),this.write(e.zb),this.write("</span>"));0<e.ia.length&&
+this.write("</a>");this.write("</div></td></tr>\n")}yd(this,b,c,a);this.write("</table>\n");return this.ib.join("")};$.prototype.ic=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,zb:e,ia:g})};ma("Gantt",$);$.prototype.add_bar=$.prototype.ic;$.prototype.draw=$.prototype.jc;})();
diff --git a/google/appengine/ext/blobstore/blobstore.py b/google/appengine/ext/blobstore/blobstore.py
index 909c1d3..97461f5 100644
--- a/google/appengine/ext/blobstore/blobstore.py
+++ b/google/appengine/ext/blobstore/blobstore.py
@@ -270,9 +270,9 @@
     """
     return self.__key
 
-  def delete(self):
+  def delete(self, _token=None):
     """Permanently delete blob from Blobstore."""
-    delete(self.key())
+    delete(self.key(), _token=_token)
 
   def open(self, *args, **kwargs):
     """Returns a BlobReader for this blob.
diff --git a/google/appengine/ext/cloudstorage/cloudstorage_stub.py b/google/appengine/ext/cloudstorage/cloudstorage_stub.py
index 2332cae..fbb15ef 100644
--- a/google/appengine/ext/cloudstorage/cloudstorage_stub.py
+++ b/google/appengine/ext/cloudstorage/cloudstorage_stub.py
@@ -516,7 +516,7 @@
         for info in q.run():
           if not info.filename.startswith(name):
             break
-        if info.filename.startswith(name):
+        if info is not None and info.filename.startswith(name):
           info = None
       else:
         info = q.get()
diff --git a/google/appengine/ext/datastore_admin/backup_handler.py b/google/appengine/ext/datastore_admin/backup_handler.py
index 8ba477e..849d1de 100644
--- a/google/appengine/ext/datastore_admin/backup_handler.py
+++ b/google/appengine/ext/datastore_admin/backup_handler.py
@@ -956,6 +956,7 @@
   blob_files = db.StringListProperty()
   original_app = db.StringProperty(default=None)
   gs_handle = db.TextProperty(default=None)
+  destination = db.StringProperty()
 
   @classmethod
   def kind(cls):
diff --git a/google/appengine/ext/datastore_admin/static/js/compiled.js b/google/appengine/ext/datastore_admin/static/js/compiled.js
index b92385a..cf1c413 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,aa=parseInt,n=String;function p(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",y="type",da="name",z="length",ea="propertyIsEnumerable",A="prototype",fa="checked",B="split",C="style",ga="target",D="call",E="apply",F,G=this,H=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[A].toString[D](a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a[z]&&"undefined"!=typeof a.splice&&
 "undefined"!=typeof a[ea]&&!a[ea]("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a[D]&&"undefined"!=typeof a[ea]&&!a[ea]("call"))return"function"}else return"null";else if("function"==b&&"undefined"==typeof a[D])return"object";return b},ha=function(a){var b=H(a);return"array"==b||"object"==b&&"number"==typeof a[z]},I=function(a){return"string"==typeof a},ia=function(a){var b=typeof a;return"object"==b&&null!=a||"function"==b},ja=function(a,b){var c=k[A][t][D](arguments,1);
-return function(){var b=c[t]();b[r][E](b,arguments);return a[E](this,b)}},ka=function(a,b){function c(){}c.prototype=b[A];a.s=b[A];a.prototype=new c;a.w=function(a,c,e){var h=k[A][t][D](arguments,2);return b[A][c][E](a,h)}};var J=function(a){l.captureStackTrace?l.captureStackTrace(this,J):this.stack=l().stack||"";a&&(this.message=n(a))};ka(J,l);J[A].name="CustomError";var la=function(a,b){for(var c=a[B]("%s"),d="",f=k[A][t][D](arguments,1);f[z]&&1<c[z];)d+=c[s]()+f[s]();return d+c.join("%s")},sa=function(a,b){if(b)return a[u](ma,"&amp;")[u](na,"&lt;")[u](oa,"&gt;")[u](pa,"&quot;")[u](qa,"&#39;");if(!ra.test(a))return a;-1!=a[w]("&")&&(a=a[u](ma,"&amp;"));-1!=a[w]("<")&&(a=a[u](na,"&lt;"));-1!=a[w](">")&&(a=a[u](oa,"&gt;"));-1!=a[w]('"')&&(a=a[u](pa,"&quot;"));-1!=a[w]("'")&&(a=a[u](qa,"&#39;"));return a},ma=/&/g,na=/</g,oa=/>/g,pa=/"/g,qa=/'/g,ra=/[&<>"']/,ta=
-function(a,b){return a<b?-1:a>b?1:0},ua=function(a){return n(a)[u](/\-([a-z])/g,function(a,c){return c.toUpperCase()})},va=function(a,b){var c=I(b)?n(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 wa=function(a,b){b.unshift(a);J[D](this,la[E](null,b));b[s]()};ka(wa,J);wa[A].name="AssertionError";var K=function(a,b,c){if(!a){var d=k[A][t][D](arguments,2),f="Assertion failed";if(b)var f=f+(": "+b),e=d;throw new wa(""+f,e||[]);}return a};var L=k[A],xa=L[w]?function(a,b,c){K(null!=a[z]);return L[w][D](a,b,c)}:function(a,b,c){c=null==c?0:0>c?Math.max(0,a[z]+c):c;if(I(a))return I(b)&&1==b[z]?a[w](b,c):-1;for(;c<a[z];c++)if(c in a&&a[c]===b)return c;return-1},ya=L.forEach?function(a,b,c){K(null!=a[z]);L.forEach[D](a,b,c)}:function(a,b,c){for(var d=a[z],f=I(a)?a[B](""):a,e=0;e<d;e++)e in f&&b[D](c,f[e],e,a)},za=function(a){var b=a[z];if(0<b){for(var c=k(b),d=0;d<b;d++)c[d]=a[d];return c}return[]},Aa=function(a,b,c){K(null!=a[z]);return 2>=
+return function(){var b=c[t]();b[r][E](b,arguments);return a[E](this,b)}},ka=function(a,b){function c(){}c.prototype=b[A];a.t=b[A];a.prototype=new c;a.A=function(a,c,e){return b[A][c][E](a,k[A][t][D](arguments,2))}};var J=function(a){if(l.captureStackTrace)l.captureStackTrace(this,J);else{var b=l().stack;b&&(this.stack=b)}a&&(this.message=n(a))};ka(J,l);J[A].name="CustomError";var la=function(a,b){for(var c=a[B]("%s"),d="",f=k[A][t][D](arguments,1);f[z]&&1<c[z];)d+=c[s]()+f[s]();return d+c.join("%s")},sa=function(a,b){if(b)return a[u](ma,"&amp;")[u](na,"&lt;")[u](oa,"&gt;")[u](pa,"&quot;")[u](qa,"&#39;");if(!ra.test(a))return a;-1!=a[w]("&")&&(a=a[u](ma,"&amp;"));-1!=a[w]("<")&&(a=a[u](na,"&lt;"));-1!=a[w](">")&&(a=a[u](oa,"&gt;"));-1!=a[w]('"')&&(a=a[u](pa,"&quot;"));-1!=a[w]("'")&&(a=a[u](qa,"&#39;"));return a},ma=/&/g,na=/</g,oa=/>/g,pa=/"/g,qa=/'/g,ra=/[&<>"']/,ta=
+function(a,b){return a<b?-1:a>b?1:0},ua=function(a){return n(a)[u](/\-([a-z])/g,function(a,c){return c.toUpperCase()})},va=function(a,b){var c=I(b)?n(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 wa=function(a,b){b.unshift(a);J[D](this,la[E](null,b));b[s]()};ka(wa,J);wa[A].name="AssertionError";var K=function(a,b,c){if(!a){var d="Assertion failed";if(b)var d=d+(": "+b),f=k[A][t][D](arguments,2);throw new wa(""+d,f||[]);}return a};var L=k[A],xa=L[w]?function(a,b,c){K(null!=a[z]);return L[w][D](a,b,c)}:function(a,b,c){c=null==c?0:0>c?Math.max(0,a[z]+c):c;if(I(a))return I(b)&&1==b[z]?a[w](b,c):-1;for(;c<a[z];c++)if(c in a&&a[c]===b)return c;return-1},ya=L.forEach?function(a,b,c){K(null!=a[z]);L.forEach[D](a,b,c)}:function(a,b,c){for(var d=a[z],f=I(a)?a[B](""):a,e=0;e<d;e++)e in f&&b[D](c,f[e],e,a)},za=function(a){var b=a[z];if(0<b){for(var c=k(b),d=0;d<b;d++)c[d]=a[d];return c}return[]},Aa=function(a,b,c){K(null!=a[z]);return 2>=
 arguments[z]?L[t][D](a,b):L[t][D](a,b,c)};var Ba=function(a,b,c){for(var d in a)b[D](c,a[d],d,a)},Ca="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),Da=function(a,b){for(var c,d,f=1;f<arguments[z];f++){d=arguments[f];for(c in d)a[c]=d[c];for(var e=0;e<Ca[z];e++)c=Ca[e],Object[A].hasOwnProperty[D](d,c)&&(a[c]=d[c])}};var M,Ea,Fa,Ga,Ha=function(){return G.navigator?G.navigator.userAgent:null};Ga=Fa=Ea=M=!1;var N;if(N=Ha()){var Ia=G.navigator;M=0==N.lastIndexOf("Opera",0);Ea=!M&&(-1!=N[w]("MSIE")||-1!=N[w]("Trident"));Fa=!M&&-1!=N[w]("WebKit");Ga=!M&&!Fa&&!Ea&&"Gecko"==Ia.product}var Ja=M,O=Ea,P=Ga,Q=Fa,Ka=function(){var a=G.document;return a?a.documentMode:void 0},La;
 t:{var Ma="",R;if(Ja&&G.opera)var Na=G.opera.version,Ma="function"==typeof Na?Na():Na;else if(P?R=/rv\:([^\);]+)(\)|;)/:O?R=/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/:Q&&(R=/WebKit\/(\S+)/),R)var Oa=R.exec(Ha()),Ma=Oa?Oa[1]:"";if(O){var Pa=Ka();if(Pa>parseFloat(Ma)){La=n(Pa);break t}}La=Ma}
 var Qa=La,Ra={},S=function(a){var b;if(!(b=Ra[a])){b=0;for(var c=n(Qa)[u](/^[\s\xa0]+|[\s\xa0]+$/g,"")[B]("."),d=n(a)[u](/^[\s\xa0]+|[\s\xa0]+$/g,"")[B]("."),f=Math.max(c[z],d[z]),e=0;0==b&&e<f;e++){var h=c[e]||"",m=d[e]||"",lb=RegExp("(\\d*)(\\D*)","g"),mb=RegExp("(\\d*)(\\D*)","g");do{var X=lb.exec(h)||["","",""],Y=mb.exec(m)||["","",""];if(0==X[0][z]&&0==Y[0][z])break;b=ta(0==X[1][z]?0:aa(X[1],10),0==Y[1][z]?0:aa(Y[1],10))||ta(0==X[2][z],0==Y[2][z])||ta(X[2],Y[2])}while(0==b)}b=Ra[a]=0<=b}return b},
 Sa=G.document,Ta=Sa&&O?Ka()||("CSS1Compat"==Sa.compatMode?aa(Qa,10):5):void 0;var Ua=!O||O&&9<=Ta;!P&&!O||O&&O&&9<=Ta||P&&S("1.9.1");O&&S("9");var Va=function(a,b){var c;c=a.className;c=I(c)&&c.match(/\S+/g)||[];for(var d=Aa(arguments,1),f=c[z]+d[z],e=c,h=0;h<d[z];h++)0<=xa(e,d[h])||e[r](d[h]);a.className=c.join(" ");return c[z]==f};var T=function(a,b){return I(b)?a.getElementById(b):b},Wa=function(a,b,c,d){a=d||a;var f=b&&"*"!=b?b.toUpperCase():"";if(a.querySelectorAll&&a.querySelector&&(f||c))return a.querySelectorAll(f+(c?"."+c:""));if(c&&a.getElementsByClassName){b=a.getElementsByClassName(c);if(f){a={};for(var e=d=0,h;h=b[e];e++)f==h.nodeName&&(a[d++]=h);a.length=d;return a}return b}b=a.getElementsByTagName(f||"*");if(c){a={};for(e=d=0;h=b[e];e++){var f=h.className,m;if(m="function"==typeof f[B])m=0<=xa(f[B](/\s+/),c);m&&
 (a[d++]=h)}a.length=d;return a}return b},Ya=function(a,b){Ba(b,function(b,d){"style"==d?a[C].cssText=b:"class"==d?a.className=b:"for"==d?a.htmlFor=b:d in Xa?a.setAttribute(Xa[d],b):0==d.lastIndexOf("aria-",0)||0==d.lastIndexOf("data-",0)?a.setAttribute(d,b):a[d]=b})},Xa={cellpadding:"cellPadding",cellspacing:"cellSpacing",colspan:"colSpan",frameborder:"frameBorder",height:"height",maxlength:"maxLength",role:"role",rowspan:"rowSpan",type:"type",usemap:"useMap",valign:"vAlign",width:"width"},$a=function(a,
 b,c){var d=arguments,f=d[0],e=d[1];if(!Ua&&e&&(e[da]||e[y])){f=["<",f];e[da]&&f[r](' name="',sa(e[da]),'"');if(e[y]){f[r](' type="',sa(e[y]),'"');var h={};Da(h,e);delete h[y];e=h}f[r](">");f=f.join("")}f=g.createElement(f);e&&(I(e)?f.className=e:"array"==H(e)?Va[E](null,[f].concat(e)):Ya(f,e));2<d[z]&&Za(g,f,d,2);return f},Za=function(a,b,c,d){function f(c){c&&b.appendChild(I(c)?a.createTextNode(c):c)}for(;d<c[z];d++){var e=c[d];if(!ha(e)||ia(e)&&0<e.nodeType)f(e);else{var h;t:{if(e&&"number"==typeof e[z]){if(ia(e)){h=
-"function"==typeof e.item||"string"==typeof e.item;break t}if("function"==H(e)){h="function"==typeof e.item;break t}}h=!1}ya(h?za(e):e,f)}}};var ab=function(a){var b=a[y];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[z]?b:null;default:return void 0!==a[v]?a[v]:null}};var bb=function(a){bb[" "](a);return a};bb[" "]=function(){};var cb=!O||O&&9<=Ta,db=O&&!S("9");!Q||S("528");P&&S("1.9b")||O&&S("8")||Ja&&S("9.5")||Q&&S("528");P&&!S("8")||O&&S("9");var eb=function(a,b){this.type=a;this.target=b;p(this,this[ga]);this.defaultPrevented=this.n=!1};eb[A].preventDefault=function(){this.defaultPrevented=!0};var U=function(a,b){eb[D](this,a?a[y]:"");this.target=null;p(this,null);this.relatedTarget=null;this.button=this.screenY=this.screenX=this.clientY=this.clientX=this.offsetY=this.offsetX=0;ba(this,0);this.charCode=0;this.metaKey=this.shiftKey=this.altKey=this.ctrlKey=!1;this.o=this.state=null;a&&this.t(a,b)};ka(U,eb);
-U[A].t=function(a,b){var c=this.type=a[y];this.target=a[ga]||a.srcElement;p(this,b);var d=a.relatedTarget;if(d){if(P){var f;t:{try{bb(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=Q||void 0!==a.offsetX?a.offsetX:a.layerX;this.offsetY=Q||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]()};U[A].preventDefault=function(){U.s[ca][D](this);var a=this.o;if(a[ca])a[ca]();else if(a.returnValue=!1,db)try{(a.ctrlKey||112<=a[x]&&123>=a[x])&&ba(a,-1)}catch(b){}};var fb="closure_listenable_"+(1E6*Math.random()|0),gb=function(a){try{return!(!a||!a[fb])}catch(b){return!1}},hb=0;var ib=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=++hb;this.e=this.k=!1};ib[A].m=function(){this.e=!0;this.j=this.src=this.g=this.c=null};var jb=function(a){this.src=a;this.a={};this.l=0};jb[A].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[z];++h){var m=e[h];if(!m.e&&m.c==b&&m.capture==!!d&&m.j==f)break t}h=-1}-1<h?(a=e[h],c||(a.k=!1)):(a=new ib(b,null,this.src,a,!!d,f),a.k=c,e[r](a));return a};jb[A].p=function(a){var b=a[y];if(!(b in this.a))return!1;var c=this.a[b],d=xa(c,a),f;if(f=0<=d)K(null!=c[z]),L.splice[D](c,d,1);f&&(a.m(),0==this.a[b][z]&&(delete this.a[b],this.l--));return f};var kb="closure_lm_"+(1E6*Math.random()|0),V={},nb=0,ob=function(a,b,c,d,f){if("array"==H(b)){for(var e=0;e<b[z];e++)ob(a,b[e],c,d,f);return null}c=pb(c);if(gb(a))a=a.v(b,c,d,f);else{if(!b)throw l("Invalid event type");var e=!!d,h=qb(a);h||(a[kb]=h=new jb(a));c=h.add(b,c,!1,d,f);c.g||(d=rb(),c.g=d,d.src=a,d.c=c,a.addEventListener?a.addEventListener(b,d,e):a.attachEvent(b in V?V[b]:V[b]="on"+b,d),nb++);a=c}return a},rb=function(){var a=sb,b=cb?function(c){return a[D](b.src,b.c,c)}:function(c){c=a[D](b.src,
-b.c,c);if(!c)return c};return b},ub=function(a,b,c,d){var f=1;if(a=qb(a))if(b=a.a[b])for(b=za(b),a=0;a<b[z];a++){var e=b[a];e&&e.capture==c&&!e.e&&(f&=!1!==tb(e,d))}return Boolean(f)},tb=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(gb(f))f.u(a);else{var e=a[y],h=a.g;f.removeEventListener?f.removeEventListener(e,h,a.capture):f.detachEvent&&f.detachEvent(e in V?V[e]:V[e]="on"+e,h);nb--;(e=qb(f))?(e.p(a),0==e.l&&(e.src=null,f[kb]=null)):a.m()}}return c[D](d,
-b)},sb=function(a,b){if(a.e)return!0;if(!cb){var c;if(!(c=b))t:{c=["window","event"];for(var d=G,f;f=c[s]();)if(null!=d[f])d=d[f];else{c=null;break t}c=d}f=c;c=new U(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[y],m=f[z]-1;!c.n&&0<=m;m--)p(c,f[m]),d&=ub(f[m],e,!0,c);for(m=0;!c.n&&m<f[z];m++)p(c,f[m]),d&=ub(f[m],e,!1,c)}return d}return tb(a,
-new U(b,this))},qb=function(a){a=a[kb];return a instanceof jb?a:null},vb="__closure_events_fn_"+(1E9*Math.random()>>>0),pb=function(a){K(a,"Listener can not be null.");if("function"==H(a))return a;K(a.handleEvent,"An object listener must have handleEvent method.");return a[vb]||(a[vb]=function(b){return a.handleEvent(b)})};var wb=function(a,b,c){var d;t:if(d=ua(c),void 0===a[C][d]&&(c=(Q?"Webkit":P?"Moz":O?"ms":Ja?"O":null)+va(c),void 0!==a[C][c])){d=c;break t}d&&(a[C][d]=b)};var xb=function(a,b){var c=[];1<arguments[z]&&(c=k[A][t][D](arguments)[t](1));var d=Wa(g,"th","tct-selectall",a);if(0!=d[z]){var d=d[0],f=0,e=Wa(g,"tbody",null,a);e[z]&&(f=e[0].rows[z]);this.d=$a("input",{type:"checkbox"});d.appendChild(this.d);f?ob(this.d,"click",this.r,!1,this):q(this.d,!0);this.f=[];this.h=[];this.i=[];d=Wa(g,"input",null,a);for(f=0;e=d[f];f++)"checkbox"==e[y]&&e!=this.d?(this.f[r](e),ob(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))}};
-F=xb[A];F.f=null;F.b=0;F.d=null;F.h=null;F.i=null;F.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[z]: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)};F.q=function(a){this.b+=a[ga][fa]?1:-1;this.d.checked=this.b==this.f[z];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 yb=function(){var a=T(g,"kinds");a&&new xb(a);(a=T(g,"pending_backups"))&&new xb(a);(a=T(g,"backups"))&&new xb(a,"Restore");var b=T(g,"ae-datastore-admin-filesystem");b&&ob(b,"change",function(){var a="gs"==ab(b);T(g,"gs_bucket_tr")[C].display=a?"":"none"});if(a=T(g,"confirm_delete_form")){var c=T(g,"confirm_readonly_delete");c&&(a.onsubmit=function(){var a=T(g,"confirm_message");I("color")?wb(a,"red","color"):Ba("color",ja(wb,a));return c[fa]})}},W=["ae","Datastore","Admin","init"],Z=G;
+"function"==typeof e.item||"string"==typeof e.item;break t}if("function"==H(e)){h="function"==typeof e.item;break t}}h=!1}ya(h?za(e):e,f)}}};var ab=function(a){var b=a[y];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[z]?b:null;default:return void 0!==a[v]?a[v]:null}};var bb=function(a){bb[" "](a);return a};bb[" "]=function(){};var cb=!O||O&&9<=Ta,db=O&&!S("9");!Q||S("528");P&&S("1.9b")||O&&S("8")||Ja&&S("9.5")||Q&&S("528");P&&!S("8")||O&&S("9");var eb=function(a,b){this.type=a;this.target=b;p(this,this[ga]);this.defaultPrevented=this.o=!1};eb[A].preventDefault=function(){this.defaultPrevented=!0};var U=function(a,b){eb[D](this,a?a[y]:"");this.target=null;p(this,null);this.relatedTarget=null;this.button=this.screenY=this.screenX=this.clientY=this.clientX=this.offsetY=this.offsetX=0;ba(this,0);this.charCode=0;this.metaKey=this.shiftKey=this.altKey=this.ctrlKey=!1;this.p=this.state=null;a&&this.u(a,b)};ka(U,eb);
+U[A].u=function(a,b){var c=this.type=a[y];this.target=a[ga]||a.srcElement;p(this,b);var d=a.relatedTarget;if(d){if(P){var f;t:{try{bb(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=Q||void 0!==a.offsetX?a.offsetX:a.layerX;this.offsetY=Q||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.p=a;a.defaultPrevented&&this[ca]()};U[A].preventDefault=function(){U.t[ca][D](this);var a=this.p;if(a[ca])a[ca]();else if(a.returnValue=!1,db)try{(a.ctrlKey||112<=a[x]&&123>=a[x])&&ba(a,-1)}catch(b){}};var fb="closure_listenable_"+(1E6*Math.random()|0),gb=function(a){try{return!(!a||!a[fb])}catch(b){return!1}},hb=0;var ib=function(a,b,c,d,f,e){this.c=a;this.g=b;this.src=c;this.type=d;this.k=!!f;this.j=e;this.key=++hb;this.e=this.l=!1};ib[A].n=function(){this.e=!0;this.j=this.src=this.g=this.c=null};var jb=function(a){this.src=a;this.a={};this.m=0};jb[A].add=function(a,b,c,d,f){var e=a.toString();a=this.a[e];a||(a=this.a[e]=[],this.m++);var h;t:{for(h=0;h<a[z];++h){var m=a[h];if(!m.e&&m.c==b&&m.k==!!d&&m.j==f)break t}h=-1}-1<h?(b=a[h],c||(b.l=!1)):(b=new ib(b,null,this.src,e,!!d,f),b.l=c,a[r](b));return b};jb[A].q=function(a){var b=a[y];if(!(b in this.a))return!1;var c=this.a[b],d=xa(c,a),f;if(f=0<=d)K(null!=c[z]),L.splice[D](c,d,1);f&&(a.n(),0==this.a[b][z]&&(delete this.a[b],this.m--));return f};var kb="closure_lm_"+(1E6*Math.random()|0),V={},nb=0,ob=function(a,b,c,d,f){if("array"==H(b)){for(var e=0;e<b[z];e++)ob(a,b[e],c,d,f);return null}c=pb(c);if(gb(a))a=a.w(b,c,d,f);else{if(!b)throw l("Invalid event type");var e=!!d,h=qb(a);h||(a[kb]=h=new jb(a));c=h.add(b,c,!1,d,f);c.g||(d=rb(),c.g=d,d.src=a,d.c=c,a.addEventListener?a.addEventListener(b,d,e):a.attachEvent(b in V?V[b]:V[b]="on"+b,d),nb++);a=c}return a},rb=function(){var a=sb,b=cb?function(c){return a[D](b.src,b.c,c)}:function(c){c=a[D](b.src,
+b.c,c);if(!c)return c};return b},ub=function(a,b,c,d){var f=1;if(a=qb(a))if(b=a.a[b])for(b=za(b),a=0;a<b[z];a++){var e=b[a];e&&e.k==c&&!e.e&&(f&=!1!==tb(e,d))}return Boolean(f)},tb=function(a,b){var c=a.c,d=a.j||a.src;if(a.l&&"number"!=typeof a&&a&&!a.e){var f=a.src;if(gb(f))f.v(a);else{var e=a[y],h=a.g;f.removeEventListener?f.removeEventListener(e,h,a.k):f.detachEvent&&f.detachEvent(e in V?V[e]:V[e]="on"+e,h);nb--;(e=qb(f))?(e.q(a),0==e.m&&(e.src=null,f[kb]=null)):a.n()}}return c[D](d,b)},sb=function(a,
+b){if(a.e)return!0;if(!cb){var c;if(!(c=b))t:{c=["window","event"];for(var d=G,f;f=c[s]();)if(null!=d[f])d=d[f];else{c=null;break t}c=d}f=c;c=new U(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[y],m=f[z]-1;!c.o&&0<=m;m--)p(c,f[m]),d&=ub(f[m],e,!0,c);for(m=0;!c.o&&m<f[z];m++)p(c,f[m]),d&=ub(f[m],e,!1,c)}return d}return tb(a,new U(b,
+this))},qb=function(a){a=a[kb];return a instanceof jb?a:null},vb="__closure_events_fn_"+(1E9*Math.random()>>>0),pb=function(a){K(a,"Listener can not be null.");if("function"==H(a))return a;K(a.handleEvent,"An object listener must have handleEvent method.");return a[vb]||(a[vb]=function(b){return a.handleEvent(b)})};var wb=function(a,b,c){var d;t:if(d=ua(c),void 0===a[C][d]&&(c=(Q?"Webkit":P?"Moz":O?"ms":Ja?"O":null)+va(c),void 0!==a[C][c])){d=c;break t}d&&(a[C][d]=b)};var xb=function(a,b){var c=[];1<arguments[z]&&(c=k[A][t][D](arguments)[t](1));var d=Wa(g,"th","tct-selectall",a);if(0!=d[z]){var d=d[0],f=0,e=Wa(g,"tbody",null,a);e[z]&&(f=e[0].rows[z]);this.d=$a("input",{type:"checkbox"});d.appendChild(this.d);f?ob(this.d,"click",this.s,!1,this):q(this.d,!0);this.f=[];this.h=[];this.i=[];d=Wa(g,"input",null,a);for(f=0;e=d[f];f++)"checkbox"==e[y]&&e!=this.d?(this.f[r](e),ob(e,"click",this.r,!1,this)):"action"==e[da]&&(0<=c[w](e[v])?this.i[r](e):this.h[r](e),q(e,!0))}};
+F=xb[A];F.f=null;F.b=0;F.d=null;F.h=null;F.i=null;F.s=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[z]: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)};F.r=function(a){this.b+=a[ga][fa]?1:-1;this.d.checked=this.b==this.f[z];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 yb=function(){var a=T(g,"kinds");a&&new xb(a);(a=T(g,"pending_backups"))&&new xb(a);(a=T(g,"backups"))&&new xb(a,"Restore");var b=T(g,"ae-datastore-admin-filesystem");b&&ob(b,"change",function(){var a="gs"==ab(b);T(g,"gs_bucket_tr")[C].display=a?"":"none"});if(a=T(g,"confirm_delete_form")){var c=T(g,"confirm_readonly_delete");c&&(a.onsubmit=function(){var a=T(g,"confirm_message");I("color")?wb(a,"red","color"):Ba("color",ja(wb,a));return c[fa]})}},W=["ae","Datastore","Admin","init"],Z=G;
 W[0]in Z||!Z.execScript||Z.execScript("var "+W[0]);for(var $;W[z]&&($=W[s]());)W[z]||void 0===yb?Z=Z[$]?Z[$]:Z[$]={}:Z[$]=yb;
diff --git a/google/appengine/ext/mapreduce/api/__init__.py b/google/appengine/ext/mapreduce/api/__init__.py
new file mode 100644
index 0000000..c33ae80
--- /dev/null
+++ b/google/appengine/ext/mapreduce/api/__init__.py
@@ -0,0 +1,16 @@
+#!/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.
+#
diff --git a/google/appengine/ext/mapreduce/api/map_job/__init__.py b/google/appengine/ext/mapreduce/api/map_job/__init__.py
new file mode 100644
index 0000000..9347a8b
--- /dev/null
+++ b/google/appengine/ext/mapreduce/api/map_job/__init__.py
@@ -0,0 +1,30 @@
+#!/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.
+#
+"""Map job package."""
+
+
+
+
+
+
+
+from .map_job_config import JobConfig
+from .map_job_context import JobContext
+from .map_job_context import ShardContext
+from .map_job_context import SliceContext
+from .map_job_control import Job
+from .mapper import Mapper
diff --git a/google/appengine/ext/mapreduce/api/map_job/map_job_config.py b/google/appengine/ext/mapreduce/api/map_job/map_job_config.py
new file mode 100644
index 0000000..5239cb1
--- /dev/null
+++ b/google/appengine/ext/mapreduce/api/map_job/map_job_config.py
@@ -0,0 +1,209 @@
+#!/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.
+#
+"""Per job config for map jobs."""
+from google.appengine.ext.mapreduce import hooks
+from google.appengine.ext.mapreduce import input_readers
+from google.appengine.ext.mapreduce import output_writers
+from google.appengine.ext.mapreduce import parameters
+from google.appengine.ext.mapreduce import util
+from google.appengine.ext.mapreduce.api.map_job import mapper as mapper_module
+
+
+
+
+
+_Option = parameters._Option
+
+
+
+_API_VERSION = 1
+
+
+class JobConfig(parameters._Config):
+  """Configurations for a map job.
+
+  Names started with _ are reserved for internal use.
+
+  To create an instance:
+  all option names can be used as keys to __init__.
+  If an option is required, the key must be provided.
+  If an option isn't required and no value is given, the default value
+  will be used.
+  """
+
+  job_name = _Option(basestring, required=True)
+
+
+
+
+  job_id = _Option(basestring, default_factory=util._get_descending_key)
+
+
+  mapper = _Option(mapper_module.Mapper, required=True)
+
+
+
+
+
+  input_reader_cls = _Option(input_readers.InputReader, required=True)
+
+
+  input_reader_params = _Option(dict, default_factory=lambda: {})
+
+
+  output_writer_cls = _Option(output_writers.OutputWriter,
+                              can_be_none=True)
+
+
+  output_writer_params = _Option(dict, default_factory=lambda: {})
+
+
+
+
+  shard_count = _Option(int,
+                        default_factory=lambda: parameters.config.SHARD_COUNT)
+
+
+  user_params = _Option(dict, default_factory=lambda: {})
+
+
+  queue_name = _Option(
+      basestring, default_factory=lambda: parameters.config.QUEUE_NAME)
+
+
+  shard_max_attempts = _Option(
+      int, default_factory=lambda: parameters.config.SHARD_MAX_ATTEMPTS)
+
+
+
+  done_callback_url = _Option(basestring, can_be_none=True)
+
+
+  _force_writes = _Option(bool, default_factory=lambda: False)
+
+  _base_path = _Option(basestring,
+                       default_factory=lambda: parameters.config.BASE_PATH)
+
+  _task_max_attempts = _Option(
+      int, default_factory=lambda: parameters.config.TASK_MAX_ATTEMPTS)
+
+  _task_max_data_processing_attempts = _Option(
+      int, default_factory=(
+          lambda: parameters.config.TASK_MAX_DATA_PROCESSING_ATTEMPTS))
+
+  _hooks_cls = _Option(hooks.Hooks, can_be_none=True)
+
+  _app = _Option(basestring, can_be_none=True)
+
+  _api_version = _Option(int, default_factory=lambda: _API_VERSION)
+
+
+
+  def _get_mapper_params(self):
+    """Converts self to model.MapperSpec.params."""
+    return {"input_reader": self.input_reader_params,
+            "output_writer": self.output_writer_params}
+
+  def _get_mapper_spec(self):
+    """Converts self to model.MapperSpec."""
+
+    from google.appengine.ext.mapreduce import model
+
+    return model.MapperSpec(
+        handler_spec=util._obj_to_path(self.mapper),
+        input_reader_spec=util._obj_to_path(self.input_reader_cls),
+        params=self._get_mapper_params(),
+        shard_count=self.shard_count,
+        output_writer_spec=util._obj_to_path(self.output_writer_cls))
+
+  def _get_mr_params(self):
+    """Converts self to model.MapreduceSpec.params."""
+    return {"force_writes": self._force_writes,
+            "done_callback": self.done_callback_url,
+            "user_params": self.user_params,
+            "shard_max_attempts": self.shard_max_attempts,
+            "task_max_attempts": self._task_max_attempts,
+            "task_max_data_processing_attempts":
+                self._task_max_data_processing_attempts,
+            "queue_name": self.queue_name,
+            "base_path": self._base_path,
+            "app_id": self._app,
+            "api_version": self._api_version}
+
+
+
+
+
+
+  @classmethod
+  def _get_default_mr_params(cls):
+    """Gets default values for old API."""
+    cfg = cls(_lenient=True)
+    mr_params = cfg._get_mr_params()
+    mr_params["api_version"] = 0
+    return mr_params
+
+  @classmethod
+  def _to_map_job_config(cls,
+                         mr_spec,
+
+
+                         queue_name):
+    """Converts model.MapreduceSpec back to JobConfig.
+
+    This method allows our internal methods to use JobConfig directly.
+    This method also allows us to expose JobConfig as an API during execution,
+    despite that it is not saved into datastore.
+
+    Args:
+      mr_spec: model.MapreduceSpec.
+      queue_name: queue name.
+
+    Returns:
+      The JobConfig object for this job.
+    """
+    mapper_spec = mr_spec.mapper
+
+    api_version = mr_spec.params.get("api_version", 0)
+    old_api = api_version == 0
+
+
+
+
+
+    return cls(_lenient=old_api,
+               job_name=mr_spec.name,
+               job_id=mr_spec.mapreduce_id,
+
+               mapper=util.for_name(mapper_spec.handler_spec),
+               input_reader_cls=mapper_spec.input_reader_class(),
+               input_reader_params=input_readers._get_params(mapper_spec),
+               output_writer_cls=mapper_spec.output_writer_class(),
+               output_writer_params=output_writers._get_params(mapper_spec),
+               shard_count=mapper_spec.shard_count,
+               queue_name=queue_name,
+               user_params=mr_spec.params.get("user_params"),
+               shard_max_attempts=mr_spec.params.get("shard_max_attempts"),
+               done_callback_url=mr_spec.params.get("done_callback"),
+               _force_writes=mr_spec.params.get("force_writes"),
+               _base_path=mr_spec.params["base_path"],
+               _task_max_attempts=mr_spec.params.get("task_max_attempts"),
+               _task_max_data_processing_attempts=(
+                   mr_spec.params.get("task_max_data_processing_attempts")),
+               _hooks_cls=util.for_name(mr_spec.hooks_class_name),
+               _app=mr_spec.params.get("app_id"),
+               _api_version=api_version)
diff --git a/google/appengine/ext/mapreduce/api/map_job/map_job_context.py b/google/appengine/ext/mapreduce/api/map_job/map_job_context.py
new file mode 100644
index 0000000..2bb0ec0
--- /dev/null
+++ b/google/appengine/ext/mapreduce/api/map_job/map_job_context.py
@@ -0,0 +1,82 @@
+#!/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.
+#
+"""Map job execution context."""
+
+
+class JobContext(object):
+  """Context for map job."""
+
+  def __init__(self, job_config):
+    """Init.
+
+    Read only properties:
+      job_config: map_job.JobConfig for the job.
+
+    Args:
+      job_config: map_job.JobConfig.
+    """
+    self.job_config = job_config
+
+
+class ShardContext(object):
+  """Context for a shard."""
+
+  def __init__(self, job_context, shard_state):
+    """Init.
+
+    The signature of __init__ is subject to change.
+
+    Read only properties:
+      job_context: JobContext object.
+      id: str. of format job_id-shard_number.
+      number: int. shard number. 0 indexed.
+      attempt: int. The current attempt at executing this shard.
+        Starting at 1.
+
+    Args:
+      job_context: map_job.JobConfig.
+      shard_state: model.ShardState.
+    """
+    self.job_context = job_context
+    self.id = shard_state.shard_id
+    self.number = shard_state.shard_number
+    self.attempt = shard_state.retries + 1
+
+
+class SliceContext(object):
+  """Context for map job."""
+
+  def __init__(self, shard_context, shard_state):
+    """Init.
+
+    The signature of __init__ is subject to change.
+
+    Read only properties:
+      job_context: JobContext object.
+      shard_context: ShardContext object.
+      number: int. slice number. 0 indexed.
+      attempt: int. The current attempt at executing this slice.
+        starting at 1.
+
+    Args:
+      shard_context: map_job.JobConfig.
+      shard_state: model.ShardState.
+    """
+    self.job_context = shard_context.job_context
+    self.shard_context = shard_context
+    self.number = shard_state.slice_id
+    self.attempt = shard_state.slice_retries + 1
diff --git a/google/appengine/ext/mapreduce/api/map_job/map_job_control.py b/google/appengine/ext/mapreduce/api/map_job/map_job_control.py
new file mode 100644
index 0000000..2bd44e4
--- /dev/null
+++ b/google/appengine/ext/mapreduce/api/map_job/map_job_control.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.
+#
+"""User API for controlling Map job execution."""
+
+from google.appengine.api import taskqueue
+from google.appengine.datastore import datastore_rpc
+from google.appengine.ext import db
+from google.appengine.ext.mapreduce import model
+from google.appengine.ext.mapreduce import util
+from google.appengine.ext.mapreduce.api.map_job import map_job_config
+
+
+
+
+
+class Job(object):
+  """The job submitter's view of the job.
+
+  The class allows user to submit a job, control a submitted job,
+  query its state and result.
+  """
+
+  RUNNING = "running"
+  FAILED = model.MapreduceState.RESULT_FAILED
+  ABORTED = model.MapreduceState.RESULT_ABORTED
+  SUCCESS = model.MapreduceState.RESULT_SUCCESS
+
+  STATUS_ENUM = [RUNNING, FAILED, ABORTED, SUCCESS]
+
+  def __init__(self, state=None):
+    """Init the job instance representing the job with id job_id.
+
+    Do not directly call this method. Use class methods to construct
+    new instances.
+
+    Args:
+      state: model.MapreduceState.
+    """
+    self._state = state
+
+    self.job_config = map_job_config.JobConfig._to_map_job_config(
+        state.mapreduce_spec,
+        queue_name=state.mapreduce_spec.params.get("queue_name"))
+
+  @classmethod
+  def get_job_by_id(cls, job_id=None):
+    """Gets the job instance representing the job with id job_id.
+
+    Args:
+      job_id: a job id, job_config.job_id, of a submitted job.
+
+    Returns:
+      A Job instance for job_id.
+    """
+    state = cls.__get_state_by_id(job_id)
+    return cls(state)
+
+  def get_status(self):
+    """Get status enum.
+
+    Returns:
+      One of the status enum.
+    """
+    self.__update_state()
+    if self._state.active:
+      return self.RUNNING
+    else:
+      return self._state.result_status
+
+  def abort(self):
+    """Aborts the job."""
+    model.MapreduceControl.abort(self.job_config.job_id)
+
+
+  def get_outputs(self):
+    """Get outputs of this job."""
+    raise NotImplementedError()
+
+  @classmethod
+  def submit(cls, job_config, in_xg_transaction=False):
+    """Submit the job to run.
+
+    Args:
+      job_config: an instance of map_job.MapJobConfig.
+      in_xg_transaction: controls what transaction scope to use to start this MR
+        job. If True, there has to be an already opened cross-group transaction
+        scope. MR will use one entity group from it.
+        If False, MR will create an independent transaction to start the job
+        regardless of any existing transaction scopes.
+
+    Return:
+      a Job instance representing the submitted job.
+    """
+    cls.__validate_job_config(job_config)
+    mapper_spec = job_config._get_mapper_spec()
+
+
+    mapreduce_params = job_config._get_mr_params()
+    mapreduce_spec = model.MapreduceSpec(
+        job_config.job_name,
+        job_config.job_id,
+        mapper_spec.to_json(),
+        mapreduce_params,
+        util._obj_to_path(job_config._hooks_cls))
+
+
+    if in_xg_transaction:
+      propagation = db.MANDATORY
+    else:
+      propagation = db.INDEPENDENT
+
+    state = None
+    @db.transactional(propagation=propagation)
+    def _txn():
+      state = cls.__create_and_save_state(job_config, mapreduce_spec)
+      cls.__add_kickoff_task(job_config, mapreduce_spec)
+      return state
+
+    state = _txn()
+    return cls(state)
+
+  def __update_state(self):
+    """Fetches most up to date state from db."""
+
+    if self._state.active:
+      self._state = self.__get_state_by_id(self.job_config.job_id)
+
+  @classmethod
+  def __get_state_by_id(cls, job_id):
+    """Get job state by id.
+
+    Args:
+      job_id: job id.
+
+    Returns:
+      model.MapreduceState for the job.
+
+    Raises:
+      ValueError: if the job state is missing.
+    """
+    state = model.MapreduceState.get_by_job_id(job_id)
+    if state is None:
+      raise ValueError("Job state for job %s is missing." % job_id)
+    return state
+
+  @classmethod
+  def __validate_job_config(cls, job_config):
+
+    mapper_spec = job_config._get_mapper_spec()
+    job_config.input_reader_cls.validate(mapper_spec)
+    if job_config.output_writer_cls:
+      job_config.output_writer_cls.validate(mapper_spec)
+
+  @classmethod
+  def __create_and_save_state(cls, job_config, mapreduce_spec):
+    """Save map job state to datastore.
+
+    Save state to datastore so that UI can see it immediately.
+
+    Args:
+      job_config: map_job.JobConfig.
+      mapreduce_spec: model.MapreduceSpec.
+
+    Returns:
+      model.MapreduceState for this job.
+    """
+    state = model.MapreduceState.create_new(job_config.job_id)
+    state.mapreduce_spec = mapreduce_spec
+    state.active = True
+    state.active_shards = 0
+    state.app_id = job_config._app
+    config = datastore_rpc.Configuration(force_writes=job_config._force_writes)
+    state.put(config=config)
+    return state
+
+  @classmethod
+  def __add_kickoff_task(cls, job_config, mapreduce_spec):
+    """Add kickoff task to taskqueue.
+
+    Args:
+      job_config: map_job.JobConfig.
+      mapreduce_spec: model.MapreduceSpec,
+    """
+    params = {"mapreduce_id": job_config.job_id}
+
+    kickoff_task = taskqueue.Task(
+
+        url=job_config._base_path + "/kickoffjob_callback/" + job_config.job_id,
+        headers=util._get_task_headers(job_config.job_id),
+        params=params)
+    if job_config._hooks_cls:
+      hooks = job_config._hooks_cls(mapreduce_spec)
+      try:
+        hooks.enqueue_kickoff_task(kickoff_task, job_config.queue_name)
+        return
+      except NotImplementedError:
+        pass
+    kickoff_task.add(job_config.queue_name, transactional=True)
diff --git a/google/appengine/ext/mapreduce/api/map_job/mapper.py b/google/appengine/ext/mapreduce/api/map_job/mapper.py
new file mode 100644
index 0000000..62986e5
--- /dev/null
+++ b/google/appengine/ext/mapreduce/api/map_job/mapper.py
@@ -0,0 +1,120 @@
+#!/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.
+#
+"""Interface for user defined mapper."""
+
+
+
+
+
+class Mapper(object):
+  """Interface user's mapper should implement.
+
+  Each shard initiates one instance. The instance is pickled
+  and unpickled if a shard can't finish within the boundary of a single
+  task (a.k.a a slice of the shard).
+
+  Upon shard retry, a new instance will be used.
+
+  Upon slice retry, the instance is unpickled from its state
+  at the end of last slice.
+
+  Be wary of the size of your mapper instances. They have to be persisted
+  across slices.
+  """
+
+  def __init__(self):
+    """Init.
+
+    Init must not take additional arguments.
+    """
+    pass
+
+  def begin_shard(self, ctx):
+    """Called at the beginning of a shard.
+
+    This method may be called more than once due to slice retry.
+    Make it idempotent.
+
+    Args:
+      ctx: map_job.ShardContext object.
+    """
+    pass
+
+  def end_shard(self, ctx):
+    """Called at the end of a shard.
+
+    This method may be called more than once due to slice retry.
+    Make it idempotent.
+
+    If shard execution error out before reaching the end, this method
+    won't be called.
+
+    Args:
+      ctx: map_job.ShardContext object.
+    """
+    pass
+
+  def begin_slice(self, ctx):
+    """Called at the beginning of a slice.
+
+    This method may be called more than once due to slice retry.
+    Make it idempotent.
+
+    Args:
+      ctx: map_job.SliceContext object.
+    """
+    pass
+
+  def end_slice(self, ctx):
+    """Called at the end of a slice.
+
+    This method may be called more than once due to slice retry.
+    Make it idempotent.
+
+    If slice execution error out before reaching the end, this method
+    won't be called.
+
+    Args:
+      ctx: map_job.SliceContext object.
+    """
+    pass
+
+
+  def __call__(self, ctx, val):
+    """Invoked once on every value yielded by input reader.
+
+    Under normal cases, this is invoked exactly once on each input value.
+    But upon slice retry, some input value may have been processed by
+    the previous attempt. If your logic is idempotent (e.g write to
+    datastore by key), this is OK. If your logic is not (e.g. append to
+    a cloud storage file), slice retry can create duplicated entries.
+    Work is in progress to remove these duplicates.
+
+    Args:
+      ctx: context.Context object.
+      val: a single yielded value from your input reader. The exact type
+        depends on the input reader. For example, some may yield a single
+        datastore entity, others may yield a (int, str) tuple.
+
+    Yields:
+      Each value yielded is fed into output writer.
+      Thus each value must have the type expected by your output writer.
+
+    Returns:
+      If there is nothing to yield, return None.
+    """
+    return
diff --git a/google/appengine/ext/mapreduce/context.py b/google/appengine/ext/mapreduce/context.py
index e1e1120..d535ca5 100644
--- a/google/appengine/ext/mapreduce/context.py
+++ b/google/appengine/ext/mapreduce/context.py
@@ -384,6 +384,7 @@
     pass
 
 
+
 class Context(object):
   """MapReduce execution context.
 
@@ -420,6 +421,8 @@
     """
     self._shard_state = shard_state
     self.mapreduce_spec = mapreduce_spec
+
+
     self.task_retry_count = task_retry_count
 
     if self.mapreduce_spec:
diff --git a/google/appengine/ext/mapreduce/control.py b/google/appengine/ext/mapreduce/control.py
index 378a2ec..1515a66 100644
--- a/google/appengine/ext/mapreduce/control.py
+++ b/google/appengine/ext/mapreduce/control.py
@@ -37,14 +37,16 @@
 
 
 
+
+
 import logging
-import google
 
 from google.appengine.ext import db
 from google.appengine.ext.mapreduce import handlers
 from google.appengine.ext.mapreduce import model
 from google.appengine.ext.mapreduce import parameters
 from google.appengine.ext.mapreduce import util
+from google.appengine.ext.mapreduce.api import map_job
 
 
 def start_map(name,
@@ -63,12 +65,19 @@
               in_xg_transaction=False):
   """Start a new, mapper-only mapreduce.
 
+  Deprecated! Use map_job.start instead.
+
+  If a value can be specified both from an explicit argument and from
+  a dictionary, the value from the explicit argument wins.
+
   Args:
     name: mapreduce name. Used only for display purposes.
     handler_spec: fully qualified name of mapper handler function/class to call.
     reader_spec: fully qualified name of mapper reader to use
     mapper_parameters: dictionary of parameters to pass to mapper. These are
-      mapper-specific and also used for reader initialization.
+      mapper-specific and also used for reader/writer initialization.
+      Should have format {"input_reader": {}, "output_writer":{}}. Old
+      deprecated style does not have sub dictionaries.
     shard_count: number of shards to create.
     mapreduce_parameters: dictionary of mapreduce parameters relevant to the
       whole job.
@@ -93,17 +102,19 @@
   """
   if shard_count is None:
     shard_count = parameters.config.SHARD_COUNT
-  if base_path is None:
-    base_path = parameters.config.BASE_PATH
 
   if mapper_parameters:
     mapper_parameters = dict(mapper_parameters)
+
+
+  mr_params = map_job.JobConfig._get_default_mr_params()
   if mapreduce_parameters:
-    mapreduce_parameters = dict(mapreduce_parameters)
-    if "base_path" not in mapreduce_parameters:
-      mapreduce_parameters["base_path"] = base_path
-  else:
-    mapreduce_parameters = {"base_path": base_path}
+    mr_params.update(mapreduce_parameters)
+
+
+  if base_path:
+    mr_params["base_path"] = base_path
+  mr_params["queue_name"] = util.get_queue_name(queue_name)
 
   mapper_spec = model.MapperSpec(handler_spec,
                                  reader_spec,
@@ -118,8 +129,10 @@
   return handlers.StartJobHandler._start_map(
       name,
       mapper_spec,
-      mapreduce_parameters,
-      queue_name=util.get_queue_name(queue_name),
+      mr_params,
+
+
+      queue_name=mr_params["queue_name"],
       eta=eta,
       countdown=countdown,
       hooks_class_name=hooks_class_name,
diff --git a/google/appengine/ext/mapreduce/datastore_range_iterators.py b/google/appengine/ext/mapreduce/datastore_range_iterators.py
index d1980b2..63b0e35 100644
--- a/google/appengine/ext/mapreduce/datastore_range_iterators.py
+++ b/google/appengine/ext/mapreduce/datastore_range_iterators.py
@@ -42,14 +42,14 @@
 
 
 class RangeIteratorFactory(object):
-  """Factory to create RangeIterators."""
+  """Factory to create RangeIterator."""
 
   @classmethod
   def create_property_range_iterator(cls,
                                      p_range,
                                      ns_range,
                                      query_spec):
-    """Create a RangeIterator.
+    """Create a _PropertyRangeModelIterator.
 
     Args:
       p_range: a property_range.PropertyRange object that defines the
@@ -71,7 +71,7 @@
                                  k_ranges,
                                  query_spec,
                                  key_range_iter_cls):
-    """Create a RangeIterator.
+    """Create a _KeyRangesIterator.
 
     Args:
       k_ranges: a key_ranges._KeyRanges object.
@@ -91,13 +91,18 @@
 
 
 class RangeIterator(json_util.JsonMixin):
-  """Interface for DatastoreInputReader helper iterators.
+  """Interface for DatastoreInputReader helpers.
 
-  RangeIterator defines Python's generator interface and additional
-  marshaling functionality. Marshaling saves the state of the generator.
-  Unmarshaling guarantees any new generator created can resume where the
-  old generator left off. When the produced generator raises StopIteration,
-  the behavior of marshaling/unmarshaling is NOT defined.
+  Technically, RangeIterator is a container. It contains all datastore
+  entities that fall under a certain range (key range or proprety range).
+  It implements __iter__, which returns a generator that can iterate
+  through entities. It also implements marshalling logics. Marshalling
+  saves the state of the container so that any new generator created
+  can resume where the old generator left off.
+
+  Caveats:
+    1. Calling next() on the generators may also modify the container.
+    2. Marshlling after StopIteration is raised has undefined behavior.
   """
 
   def __iter__(self):
@@ -291,7 +296,12 @@
 
 
 class AbstractKeyRangeIterator(json_util.JsonMixin):
-  """Iterates over a single key_range.KeyRange and yields value for each key."""
+  """Iterates over a single key_range.KeyRange and yields value for each key.
+
+  All subclasses do the same thing: iterate over a single KeyRange.
+  They do so using different APIs (db, ndb, datastore) to return entities
+  of different types (db model, ndb model, datastore entity, raw proto).
+  """
 
   def __init__(self, k_range, query_spec):
     """Init.
diff --git a/google/appengine/ext/mapreduce/handlers.py b/google/appengine/ext/mapreduce/handlers.py
index 199af67..289a3a8 100644
--- a/google/appengine/ext/mapreduce/handlers.py
+++ b/google/appengine/ext/mapreduce/handlers.py
@@ -61,6 +61,7 @@
 from google.appengine.ext.mapreduce import operation
 from google.appengine.ext.mapreduce import parameters
 from google.appengine.ext.mapreduce import util
+from google.appengine.ext.mapreduce.api import map_job
 from google.appengine.runtime import apiproxy_errors
 
 
@@ -424,7 +425,18 @@
           cloudstorage.RetryParams(
               urlfetch_timeout=parameters._GCS_URLFETCH_TIMEOUT_SEC))
 
+    job_config = map_job.JobConfig._to_map_job_config(
+        spec,
+        os.environ.get("HTTP_X_APPENGINE_QUEUENAME"))
+    job_context = map_job.JobContext(job_config)
+    self.shard_context = map_job.ShardContext(job_context, shard_state)
+    self.slice_context = map_job.SliceContext(self.shard_context, shard_state)
     try:
+      if isinstance(tstate.handler, map_job.Mapper):
+        if tstate.slice_id == 0:
+          tstate.handler.begin_shard(self.shard_context)
+        tstate.handler.begin_slice(self.slice_context)
+
       if is_this_a_retry:
         task_directive = self._attempt_slice_recovery(shard_state, tstate)
 
@@ -526,6 +538,11 @@
     operation.counters.Increment(
         context.COUNTER_MAPPER_WALLTIME_MS,
         int((self._time() - self._start_time)*1000))(ctx)
+
+    if isinstance(tstate.handler, map_job.Mapper):
+      tstate.handler.end_slice(self.slice_context)
+      if finished_shard:
+        tstate.handler.end_shard(self.shard_context)
     ctx.flush()
 
     return finished_shard
@@ -549,7 +566,9 @@
 
       handler = transient_shard_state.handler
 
-      if input_reader.expand_parameters:
+      if isinstance(handler, map_job.Mapper):
+        result = handler(self.slice_context, data)
+      elif input_reader.expand_parameters:
         result = handler(*data)
       else:
         result = handler(data)
@@ -861,7 +880,7 @@
         tstate.slice_id,
         tstate.retries)
 
-    headers = util._get_task_headers(tstate.mapreduce_spec)
+    headers = util._get_task_headers(tstate.mapreduce_spec.mapreduce_id)
     headers[util._MR_SHARD_ID_TASK_HEADER] = tstate.shard_id
 
     worker_task = model.HugeTask(
@@ -1139,7 +1158,7 @@
     if done_callback:
       done_task = taskqueue.Task(
           url=done_callback,
-          headers=util._get_task_headers(mapreduce_spec,
+          headers=util._get_task_headers(mapreduce_spec.mapreduce_id,
                                          util.CALLBACK_MR_ID_TASK_HEADER),
           method=mapreduce_spec.params.get("done_callback_method", "POST"))
 
@@ -1230,7 +1249,7 @@
         name=task_name, params=task_params,
         countdown=parameters.config._CONTROLLER_PERIOD_SEC,
         parent=mapreduce_state,
-        headers=util._get_task_headers(mapreduce_spec))
+        headers=util._get_task_headers(mapreduce_spec.mapreduce_id))
 
     if not _run_task_hook(mapreduce_spec.get_hooks(),
                           "enqueue_controller_task",
@@ -1483,14 +1502,16 @@
         "mapper_params_validator", "mapper_params.")
     params = self._get_params(
         "params_validator", "params.")
-    if "base_path" not in params:
-      params["base_path"] = parameters.config.BASE_PATH
+
+
+    mr_params = map_job.JobConfig._get_default_mr_params()
+    mr_params.update(params)
+    if "queue_name" in mapper_params:
+      mr_params["queue_name"] = mapper_params["queue_name"]
 
 
     mapper_params["processing_rate"] = int(mapper_params.get(
         "processing_rate") or parameters.config.PROCESSING_RATE_PER_SEC)
-    queue_name = mapper_params["queue_name"] = util.get_queue_name(
-        mapper_params.get("queue_name", None))
 
 
     mapper_spec = model.MapperSpec(
@@ -1500,11 +1521,11 @@
         int(mapper_params.get("shard_count", parameters.config.SHARD_COUNT)),
         output_writer_spec=mapper_output_writer_spec)
 
-    mapreduce_id = type(self)._start_map(
+    mapreduce_id = self._start_map(
         mapreduce_name,
         mapper_spec,
-        params,
-        queue_name=queue_name,
+        mr_params,
+        queue_name=mr_params["queue_name"],
         _app=mapper_params.get("_app"))
     self.json_response["mapreduce_id"] = mapreduce_id
 
@@ -1654,7 +1675,7 @@
 
     kickoff_task = taskqueue.Task(
         url=base_path + "/kickoffjob_callback/" + mapreduce_spec.mapreduce_id,
-        headers=util._get_task_headers(mapreduce_spec),
+        headers=util._get_task_headers(mapreduce_spec.mapreduce_id),
         params=params,
         eta=eta,
         countdown=countdown)
@@ -1697,7 +1718,7 @@
         url=(mapreduce_spec.params["base_path"] + "/finalizejob_callback/" +
              mapreduce_spec.mapreduce_id),
         params={"mapreduce_id": mapreduce_spec.mapreduce_id},
-        headers=util._get_task_headers(mapreduce_spec))
+        headers=util._get_task_headers(mapreduce_spec.mapreduce_id))
     queue_name = util.get_queue_name(None)
     if not _run_task_hook(mapreduce_spec.get_hooks(),
                           "enqueue_controller_task",
diff --git a/google/appengine/ext/mapreduce/mapper_pipeline.py b/google/appengine/ext/mapreduce/mapper_pipeline.py
index 8347471..9fadcd1 100644
--- a/google/appengine/ext/mapreduce/mapper_pipeline.py
+++ b/google/appengine/ext/mapreduce/mapper_pipeline.py
@@ -115,7 +115,7 @@
         output_writer_spec=output_writer_spec,
         )
     self.fill(self.outputs.job_id, mapreduce_id)
-    self.set_status(console_url="%s/detail?job_id=%s" % (
+    self.set_status(console_url="%s/detail?mapreduce_id=%s" % (
         (parameters.config.BASE_PATH, mapreduce_id)))
 
   def try_cancel(self):
diff --git a/google/appengine/ext/mapreduce/model.py b/google/appengine/ext/mapreduce/model.py
index 9af2222..e057397 100644
--- a/google/appengine/ext/mapreduce/model.py
+++ b/google/appengine/ext/mapreduce/model.py
@@ -53,9 +53,6 @@
 
 import cgi
 import datetime
-import os
-import random
-import time
 import urllib
 import zlib
 
@@ -240,30 +237,6 @@
     return result
 
 
-
-_FUTURE_TIME = 2**34
-
-
-def _get_descending_key(gettime=time.time):
-  """Returns a key name lexically ordered by time descending.
-
-  This lets us have a key name for use with Datastore entities which returns
-  rows in time descending order when it is scanned in lexically ascending order,
-  allowing us to bypass index building for descending indexes.
-
-  Args:
-    gettime: Used for testing.
-
-  Returns:
-    A string with a time descending key.
-  """
-  now_descending = int((_FUTURE_TIME - gettime()) * 100)
-  request_id_hash = os.environ.get("REQUEST_ID_HASH")
-  if not request_id_hash:
-    request_id_hash = str(random.getrandbits(32))
-  return "%d%s" % (now_descending, request_id_hash)
-
-
 class CountersMap(json_util.JsonMixin):
   """Maintains map from counter name to counter value.
 
@@ -624,6 +597,7 @@
   _RESULTS = frozenset([RESULT_SUCCESS, RESULT_FAILED, RESULT_ABORTED])
 
 
+
   mapreduce_spec = json_util.JsonProperty(MapreduceSpec, indexed=False)
   active = db.BooleanProperty(default=True, indexed=False)
   last_poll_time = db.DateTimeProperty(required=True)
@@ -726,7 +700,7 @@
   @staticmethod
   def new_mapreduce_id():
     """Generate new mapreduce id."""
-    return _get_descending_key()
+    return util._get_descending_key()
 
   def __eq__(self, other):
     if not isinstance(other, self.__class__):
diff --git a/google/appengine/ext/mapreduce/parameters.py b/google/appengine/ext/mapreduce/parameters.py
index 12f2dc7..e73fda3 100644
--- a/google/appengine/ext/mapreduce/parameters.py
+++ b/google/appengine/ext/mapreduce/parameters.py
@@ -19,11 +19,164 @@
 __all__ = ["CONFIG_NAMESPACE",
            "config"]
 
+import pickle
+
+import google
+
+
+
+
+
+
+
+try:
+  from appengine_pipeline.src.pipeline import util as pipeline_util
+except ImportError:
+  pipeline_util = None
+
 from google.appengine.api import lib_config
 
 CONFIG_NAMESPACE = "mapreduce"
 
 
+
+
+
+
+class _JobConfigMeta(type):
+  """Metaclass that controls class creation."""
+
+  _OPTIONS = "_options"
+  _REQUIRED = "_required"
+
+  def __new__(mcs, classname, bases, class_dict):
+    """Creates a _Config class and modifies its class dict.
+
+    Args:
+      classname: name of the class.
+      bases: a list of base classes.
+      class_dict: original class dict.
+
+    Returns:
+      A new _Config class. The modified class will have two fields.
+      _options field is a dict from option name to _Option objects.
+      _required field is a set of required option names.
+    """
+    options = {}
+    required = set()
+    for name, option in class_dict.iteritems():
+      if isinstance(option, _Option):
+        options[name] = option
+        if option.required:
+          required.add(name)
+
+    for name in options:
+      class_dict.pop(name)
+    class_dict[mcs._OPTIONS] = options
+    class_dict[mcs._REQUIRED] = required
+    cls = type.__new__(mcs, classname, bases, class_dict)
+
+
+    if object not in bases:
+      parent_options = {}
+
+      for c in reversed(cls.__mro__):
+        if mcs._OPTIONS in c.__dict__:
+
+          parent_options.update(c.__dict__[mcs._OPTIONS])
+        if mcs._REQUIRED in c.__dict__:
+          required.update(c.__dict__[mcs._REQUIRED])
+      for k, v in parent_options.iteritems():
+        if k not in options:
+          options[k] = v
+    return cls
+
+
+class _Option(object):
+  """An option for _Config."""
+
+  def __init__(self, kind, required=False, default_factory=None,
+               can_be_none=False):
+    """Init.
+
+    Args:
+      kind: type of the option.
+      required: whether user is required to supply a value.
+      default_factory: a factory, when called, returns the default value.
+      can_be_none: whether value can be None.
+
+    Raises:
+      ValueError: if arguments aren't compatible.
+    """
+    if required and default_factory is not None:
+      raise ValueError("No default_factory value when option is required.")
+    self.kind = kind
+    self.required = required
+    self.default_factory = default_factory
+    self.can_be_none = can_be_none
+
+
+class _Config(object):
+  """Root class for all per job configuration."""
+
+  __metaclass__ = _JobConfigMeta
+
+  def __init__(self, _lenient=False, **kwds):
+    """Init.
+
+    Args:
+      _lenient: When true, no option is required.
+      **kwds: keyword arguments for options and their values.
+    """
+    self._verify_keys(kwds, _lenient)
+    self._set_values(kwds, _lenient)
+
+  def _verify_keys(self, kwds, _lenient):
+    keys = set()
+    for k in kwds:
+      if k not in self._options:
+        raise ValueError("Option %s is not supported." % (k))
+      keys.add(k)
+    if not _lenient:
+      missing = self._required - keys
+      if missing:
+        raise ValueError("Options %s are required." % tuple(missing))
+
+  def _set_values(self, kwds, _lenient):
+    for k, option in self._options.iteritems():
+      v = kwds.get(k)
+      if v is None and option.default_factory:
+        v = option.default_factory()
+      setattr(self, k, v)
+      if _lenient:
+        continue
+      if v is None and option.can_be_none:
+        continue
+      if isinstance(v, type) and not issubclass(v, option.kind):
+        raise TypeError(
+            "Expect subclass of %r for option %s. Got %r" % (
+                option.kind, k, v))
+      if not isinstance(v, type) and not isinstance(v, option.kind):
+        raise TypeError("Expect type %r for option %s. Got %r" % (
+            option.kind, k, v))
+
+  def __eq__(self, other):
+    if not isinstance(other, self.__class__):
+      return False
+    return other.__dict__ == self.__dict__
+
+  def __repr__(self):
+    return str(self.__dict__)
+
+  def to_json(self):
+    return {"config": pickle.dumps(self)}
+
+  @classmethod
+  def from_json(cls, json):
+    return pickle.loads(json["config"])
+
+
+
 class _ConfigDefaults(object):
   """Default configs.
 
@@ -86,6 +239,7 @@
   _CONTROLLER_PERIOD_SEC = 2
 
 
+
 config = lib_config.register(CONFIG_NAMESPACE, _ConfigDefaults.__dict__)
 
 
diff --git a/google/appengine/ext/mapreduce/static/detail.html b/google/appengine/ext/mapreduce/static/detail.html
index 4194cf7..20af374 100644
--- a/google/appengine/ext/mapreduce/static/detail.html
+++ b/google/appengine/ext/mapreduce/static/detail.html
@@ -5,6 +5,7 @@
   <link rel="stylesheet" href="base.css" type="text/css">
   <script type="text/javascript" src="jquery.js"></script>
   <script type="text/javascript" src="jquery-json.js"></script>
+  <script type="text/javascript" src="jquery-url.js"></script>
   <script type="text/javascript" src="status.js"></script>
   <script type="text/javascript">
     $(document).ready(initDetail);
@@ -15,8 +16,10 @@
 <div id="butter" style="display: none"></div>
 
 <div id="control">
+  <span id="overview-link">
   <a href="status">&laquo; Back to Overview</a>
   |
+  </span>
   <span id="job-control"></span>
   <!--
   Add refresh control.
@@ -44,7 +47,7 @@
   <div style="clear: both"></div>
 
   <div>
-    <h2>Mapper status</h2>
+    <h2>Shard status</h2>
     <table class="status-table">
       <thead>
         <tr>
diff --git a/google/appengine/ext/mapreduce/static/jquery.url.js b/google/appengine/ext/mapreduce/static/jquery.url.js
new file mode 100644
index 0000000..434b065
--- /dev/null
+++ b/google/appengine/ext/mapreduce/static/jquery.url.js
@@ -0,0 +1,272 @@
+/**
+ * @license
+ * JQuery URL Parser plugin, v2.2.1
+ * Developed and maintanined by Mark Perkins, mark@allmarkedup.com
+ * Source repository: https://github.com/allmarkedup/jQuery-URL-Parser
+ * Licensed under an MIT-style license. See https://github.com/allmarkedup/jQuery-URL-Parser/blob/master/LICENSE for details.
+ */
+
+;(function(factory) {
+	if (typeof define === 'function' && define.amd) {
+		// AMD available; use anonymous module
+		if ( typeof jQuery !== 'undefined' ) {
+			define(['jquery'], factory);
+		} else {
+			define([], factory);
+		}
+	} else {
+		// No AMD available; mutate global vars
+		if ( typeof jQuery !== 'undefined' ) {
+			factory(jQuery);
+		} else {
+			factory();
+		}
+	}
+})(function($, undefined) {
+
+	var tag2attr = {
+			a       : 'href',
+			img     : 'src',
+			form    : 'action',
+			base    : 'href',
+			script  : 'src',
+			iframe  : 'src',
+			link    : 'href'
+		},
+
+		key = ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'fragment'], // keys available to query
+
+		aliases = { 'anchor' : 'fragment' }, // aliases for backwards compatability
+
+		parser = {
+			strict : /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,  //less intuitive, more accurate to the specs
+			loose :  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ // more intuitive, fails on relative paths and deviates from specs
+		},
+
+		toString = Object.prototype.toString,
+
+		isint = /^[0-9]+$/;
+
+	function parseUri( url, strictMode ) {
+		var str = decodeURI( url ),
+		res   = parser[ strictMode || false ? 'strict' : 'loose' ].exec( str ),
+		uri = { attr : {}, param : {}, seg : {} },
+		i   = 14;
+
+		while ( i-- ) {
+			uri.attr[ key[i] ] = res[i] || '';
+		}
+
+		// build query and fragment parameters
+		uri.param['query'] = parseString(uri.attr['query']);
+		uri.param['fragment'] = parseString(uri.attr['fragment']);
+
+		// split path and fragement into segments
+		uri.seg['path'] = uri.attr.path.replace(/^\/+|\/+$/g,'').split('/');
+		uri.seg['fragment'] = uri.attr.fragment.replace(/^\/+|\/+$/g,'').split('/');
+
+		// compile a 'base' domain attribute
+		uri.attr['base'] = uri.attr.host ? (uri.attr.protocol ?  uri.attr.protocol+'://'+uri.attr.host : uri.attr.host) + (uri.attr.port ? ':'+uri.attr.port : '') : '';
+
+		return uri;
+	};
+
+	function getAttrName( elm ) {
+		var tn = elm.tagName;
+		if ( typeof tn !== 'undefined' ) return tag2attr[tn.toLowerCase()];
+		return tn;
+	}
+
+	function promote(parent, key) {
+		if (parent[key].length == 0) return parent[key] = {};
+		var t = {};
+		for (var i in parent[key]) t[i] = parent[key][i];
+		parent[key] = t;
+		return t;
+	}
+
+	function parse(parts, parent, key, val) {
+		var part = parts.shift();
+		if (!part) {
+			if (isArray(parent[key])) {
+				parent[key].push(val);
+			} else if ('object' == typeof parent[key]) {
+				parent[key] = val;
+			} else if ('undefined' == typeof parent[key]) {
+				parent[key] = val;
+			} else {
+				parent[key] = [parent[key], val];
+			}
+		} else {
+			var obj = parent[key] = parent[key] || [];
+			if (']' == part) {
+				if (isArray(obj)) {
+					if ('' != val) obj.push(val);
+				} else if ('object' == typeof obj) {
+					obj[keys(obj).length] = val;
+				} else {
+					obj = parent[key] = [parent[key], val];
+				}
+			} else if (~part.indexOf(']')) {
+				part = part.substr(0, part.length - 1);
+				if (!isint.test(part) && isArray(obj)) obj = promote(parent, key);
+				parse(parts, obj, part, val);
+				// key
+			} else {
+				if (!isint.test(part) && isArray(obj)) obj = promote(parent, key);
+				parse(parts, obj, part, val);
+			}
+		}
+	}
+
+	function merge(parent, key, val) {
+		if (~key.indexOf(']')) {
+			var parts = key.split('['),
+			len = parts.length,
+			last = len - 1;
+			parse(parts, parent, 'base', val);
+		} else {
+			if (!isint.test(key) && isArray(parent.base)) {
+				var t = {};
+				for (var k in parent.base) t[k] = parent.base[k];
+				parent.base = t;
+			}
+			set(parent.base, key, val);
+		}
+		return parent;
+	}
+
+	function parseString(str) {
+		return reduce(String(str).split(/&|;/), function(ret, pair) {
+			try {
+				pair = decodeURIComponent(pair.replace(/\+/g, ' '));
+			} catch(e) {
+				// ignore
+			}
+			var eql = pair.indexOf('='),
+				brace = lastBraceInKey(pair),
+				key = pair.substr(0, brace || eql),
+				val = pair.substr(brace || eql, pair.length),
+				val = val.substr(val.indexOf('=') + 1, val.length);
+
+			if ('' == key) key = pair, val = '';
+
+			return merge(ret, key, val);
+		}, { base: {} }).base;
+	}
+
+	function set(obj, key, val) {
+		var v = obj[key];
+		if (undefined === v) {
+			obj[key] = val;
+		} else if (isArray(v)) {
+			v.push(val);
+		} else {
+			obj[key] = [v, val];
+		}
+	}
+
+	function lastBraceInKey(str) {
+		var len = str.length,
+			 brace, c;
+		for (var i = 0; i < len; ++i) {
+			c = str[i];
+			if (']' == c) brace = false;
+			if ('[' == c) brace = true;
+			if ('=' == c && !brace) return i;
+		}
+	}
+
+	function reduce(obj, accumulator){
+		var i = 0,
+			l = obj.length >> 0,
+			curr = arguments[2];
+		while (i < l) {
+			if (i in obj) curr = accumulator.call(undefined, curr, obj[i], i, obj);
+			++i;
+		}
+		return curr;
+	}
+
+	function isArray(vArg) {
+		return Object.prototype.toString.call(vArg) === "[object Array]";
+	}
+
+	function keys(obj) {
+		var keys = [];
+		for ( prop in obj ) {
+			if ( obj.hasOwnProperty(prop) ) keys.push(prop);
+		}
+		return keys;
+	}
+
+	function purl( url, strictMode ) {
+		if ( arguments.length === 1 && url === true ) {
+			strictMode = true;
+			url = undefined;
+		}
+		strictMode = strictMode || false;
+		url = url || window.location.toString();
+
+		return {
+
+			data : parseUri(url, strictMode),
+
+			// get various attributes from the URI
+			attr : function( attr ) {
+				attr = aliases[attr] || attr;
+				return typeof attr !== 'undefined' ? this.data.attr[attr] : this.data.attr;
+			},
+
+			// return query string parameters
+			param : function( param ) {
+				return typeof param !== 'undefined' ? this.data.param.query[param] : this.data.param.query;
+			},
+
+			// return fragment parameters
+			fparam : function( param ) {
+				return typeof param !== 'undefined' ? this.data.param.fragment[param] : this.data.param.fragment;
+			},
+
+			// return path segments
+			segment : function( seg ) {
+				if ( typeof seg === 'undefined' ) {
+					return this.data.seg.path;
+				} else {
+					seg = seg < 0 ? this.data.seg.path.length + seg : seg - 1; // negative segments count from the end
+					return this.data.seg.path[seg];
+				}
+			},
+
+			// return fragment segments
+			fsegment : function( seg ) {
+				if ( typeof seg === 'undefined' ) {
+					return this.data.seg.fragment;
+				} else {
+					seg = seg < 0 ? this.data.seg.fragment.length + seg : seg - 1; // negative segments count from the end
+					return this.data.seg.fragment[seg];
+				}
+			}
+
+		};
+
+	};
+
+	if ( typeof $ !== 'undefined' ) {
+
+		$.fn.url = function( strictMode ) {
+			var url = '';
+			if ( this.length ) {
+				url = $(this).attr( getAttrName(this[0]) ) || '';
+			}
+			return purl( url, strictMode );
+		};
+
+		$.url = purl;
+
+	} else {
+		window.purl = purl;
+	}
+
+});
+
diff --git a/google/appengine/ext/mapreduce/static/status.js b/google/appengine/ext/mapreduce/static/status.js
index 1fc6304..21a942b 100644
--- a/google/appengine/ext/mapreduce/static/status.js
+++ b/google/appengine/ext/mapreduce/static/status.js
@@ -212,6 +212,11 @@
     url: 'command/get_job_detail',
     dataType: 'text',
     data: {'mapreduce_id': jobId},
+    statusCode: {
+      404: function() {
+        setButter('job ' + jobId + ' was not found.', true);
+      }
+    },
     error: function(request, textStatus) {
       getResponseDataJson(textStatus);
     },
@@ -283,14 +288,10 @@
   return updatedString;
 }
 
-// Retrieves the mapreduce_id from the query string. Assumes that it is
-// the only querystring parameter.
+// Retrieves the mapreduce_id from the query string.
 function getJobId() {
-  var index = window.location.search.lastIndexOf('=');
-  if (index == -1) {
-    return '';
-  }
-  return decodeURIComponent(window.location.search.substr(index+1));
+  var jobId = $.url().param('mapreduce_id');
+  return jobId == null ? '' : jobId;
 }
 
 /********* Specific to overview status page *********/
@@ -641,6 +642,9 @@
   $('#detail-page-undertext').text('Job #' + jobId);
 
   // Set control buttons.
+  if (self != top) {
+    $('#overview-link').hide();
+  }
   if (detail.active) {
     var control = $('<a href="">')
       .text('Abort Job')
diff --git a/google/appengine/ext/mapreduce/status.py b/google/appengine/ext/mapreduce/status.py
index 5492055..480e783 100644
--- a/google/appengine/ext/mapreduce/status.py
+++ b/google/appengine/ext/mapreduce/status.py
@@ -279,12 +279,13 @@
   """Handler for static resources."""
 
   _RESOURCE_MAP = {
-    "status": ("overview.html", "text/html"),
-    "detail": ("detail.html", "text/html"),
-    "base.css": ("base.css", "text/css"),
-    "jquery.js": ("jquery-1.6.1.min.js", "text/javascript"),
-    "jquery-json.js": ("jquery.json-2.2.min.js", "text/javascript"),
-    "status.js": ("status.js", "text/javascript"),
+      "status": ("overview.html", "text/html"),
+      "detail": ("detail.html", "text/html"),
+      "base.css": ("base.css", "text/css"),
+      "jquery.js": ("jquery-1.6.1.min.js", "text/javascript"),
+      "jquery-json.js": ("jquery.json-2.2.min.js", "text/javascript"),
+      "jquery-url.js": ("jquery.url.js", "text/javascript"),
+      "status.js": ("status.js", "text/javascript"),
   }
 
   def get(self, relative):
diff --git a/google/appengine/ext/mapreduce/test_support.py b/google/appengine/ext/mapreduce/test_support.py
index a411a81..b6394c1 100644
--- a/google/appengine/ext/mapreduce/test_support.py
+++ b/google/appengine/ext/mapreduce/test_support.py
@@ -36,7 +36,6 @@
 import base64
 import collections
 import logging
-import traceback
 import os
 import re
 
@@ -47,6 +46,10 @@
 
 
 
+_LOGGING_LEVEL = logging.ERROR
+logging.getLogger().setLevel(_LOGGING_LEVEL)
+
+
 def decode_task_payload(task):
   """Decodes POST task payload.
 
@@ -197,7 +200,7 @@
         task_run_counts[handler.__class__] += 1
         break
 
-      except:
+      except Exception, e:
         retries += 1
 
         if retries > 100:
@@ -208,6 +211,7 @@
             "Task %s is being retried for the %s time",
             task["name"],
             retries)
+        logging.debug(e)
 
   return task_run_counts
 
diff --git a/google/appengine/ext/mapreduce/util.py b/google/appengine/ext/mapreduce/util.py
index aecc466..63a1ced 100644
--- a/google/appengine/ext/mapreduce/util.py
+++ b/google/appengine/ext/mapreduce/util.py
@@ -52,6 +52,9 @@
 import inspect
 import os
 import pickle
+import random
+import sys
+import time
 import types
 
 import google
@@ -68,6 +71,30 @@
 CALLBACK_MR_ID_TASK_HEADER = "Mapreduce-Id"
 
 
+
+_FUTURE_TIME = 2**34
+
+
+def _get_descending_key(gettime=time.time):
+  """Returns a key name lexically ordered by time descending.
+
+  This lets us have a key name for use with Datastore entities which returns
+  rows in time descending order when it is scanned in lexically ascending order,
+  allowing us to bypass index building for descending indexes.
+
+  Args:
+    gettime: Used for testing.
+
+  Returns:
+    A string with a time descending key.
+  """
+  now_descending = int((_FUTURE_TIME - gettime()) * 100)
+  request_id_hash = os.environ.get("REQUEST_ID_HASH")
+  if not request_id_hash:
+    request_id_hash = str(random.getrandbits(32))
+  return "%d%s" % (now_descending, request_id_hash)
+
+
 def _get_task_host():
   """Get the Host header value for all mr tasks.
 
@@ -93,17 +120,18 @@
   return "%s.%s.%s" % (version, module, default_host)
 
 
-def _get_task_headers(mr_spec, mr_id_header_key=_MR_ID_TASK_HEADER):
+def _get_task_headers(map_job_id,
+                      mr_id_header_key=_MR_ID_TASK_HEADER):
   """Get headers for all mr tasks.
 
   Args:
-    mr_spec: an instance of model.MapreduceSpec.
+    map_job_id: map job id.
     mr_id_header_key: the key to set mr id with.
 
   Returns:
     A dictionary of all headers.
   """
-  return {mr_id_header_key: mr_spec.mapreduce_id,
+  return {mr_id_header_key: map_job_id,
           "Host": _get_task_host()}
 
 
@@ -177,7 +205,7 @@
     fq_name: fully qualified name of something to find
 
   Returns:
-    class object.
+    class object or None if fq_name is None.
 
   Raises:
     ImportError: when specified module could not be loaded or the class
@@ -186,6 +214,9 @@
 
 
 
+  if fq_name is None:
+    return
+
   fq_name = str(fq_name)
   module_name = __name__
   short_name = fq_name
@@ -365,3 +396,29 @@
   ndb_ctx = ndb.get_context()
   ndb_ctx.set_cache_policy(lambda key: False)
   ndb_ctx.set_memcache_policy(lambda key: False)
+
+
+def _obj_to_path(obj):
+  """Returns the fully qualified path to the object.
+
+  Args:
+    obj: obj must be a new style top level class, or a top level function.
+      No inner function or static method.
+
+  Returns:
+    Fully qualified path to the object.
+
+  Raises:
+    TypeError: when argument obj has unsupported type.
+    ValueError: when obj can't be discovered on the top level.
+  """
+  if obj is None:
+    return obj
+
+  if inspect.isclass(obj) or inspect.isfunction(obj):
+    fetched = getattr(sys.modules[obj.__module__], obj.__name__, None)
+    if fetched is None:
+      raise ValueError(
+          "Object %r must be defined on the top level of a module." % obj)
+    return "%s.%s" % (obj.__module__, obj.__name__)
+  raise TypeError("Unexpected type %s." % type(obj))
diff --git a/google/appengine/ext/remote_api/remote_api_pb.py b/google/appengine/ext/remote_api/remote_api_pb.py
index c21994b..1ac5c4e 100644
--- a/google/appengine/ext/remote_api/remote_api_pb.py
+++ b/google/appengine/ext/remote_api/remote_api_pb.py
@@ -386,6 +386,175 @@
   _STYLE = """"""
   _STYLE_CONTENT_TYPE = """"""
   _PROTO_DESCRIPTOR_NAME = 'apphosting.ext.remote_api.ApplicationError'
+class RpcError(ProtocolBuffer.ProtocolMessage):
+
+
+  UNKNOWN      =    0
+  CALL_NOT_FOUND =    1
+  PARSE_ERROR  =    2
+  SECURITY_VIOLATION =    3
+  OVER_QUOTA   =    4
+  REQUEST_TOO_LARGE =    5
+  CAPABILITY_DISABLED =    6
+  FEATURE_DISABLED =    7
+  BAD_REQUEST  =    8
+  RESPONSE_TOO_LARGE =    9
+  CANCELLED    =   10
+  REPLAY_ERROR =   11
+  DEADLINE_EXCEEDED =   12
+
+  _ErrorCode_NAMES = {
+    0: "UNKNOWN",
+    1: "CALL_NOT_FOUND",
+    2: "PARSE_ERROR",
+    3: "SECURITY_VIOLATION",
+    4: "OVER_QUOTA",
+    5: "REQUEST_TOO_LARGE",
+    6: "CAPABILITY_DISABLED",
+    7: "FEATURE_DISABLED",
+    8: "BAD_REQUEST",
+    9: "RESPONSE_TOO_LARGE",
+    10: "CANCELLED",
+    11: "REPLAY_ERROR",
+    12: "DEADLINE_EXCEEDED",
+  }
+
+  def ErrorCode_Name(cls, x): return cls._ErrorCode_NAMES.get(x, "")
+  ErrorCode_Name = classmethod(ErrorCode_Name)
+
+  has_code_ = 0
+  code_ = 0
+  has_detail_ = 0
+  detail_ = ""
+
+  def __init__(self, contents=None):
+    if contents is not None: self.MergeFromString(contents)
+
+  def code(self): return self.code_
+
+  def set_code(self, x):
+    self.has_code_ = 1
+    self.code_ = x
+
+  def clear_code(self):
+    if self.has_code_:
+      self.has_code_ = 0
+      self.code_ = 0
+
+  def has_code(self): return self.has_code_
+
+  def detail(self): return self.detail_
+
+  def set_detail(self, x):
+    self.has_detail_ = 1
+    self.detail_ = x
+
+  def clear_detail(self):
+    if self.has_detail_:
+      self.has_detail_ = 0
+      self.detail_ = ""
+
+  def has_detail(self): return self.has_detail_
+
+
+  def MergeFrom(self, x):
+    assert x is not self
+    if (x.has_code()): self.set_code(x.code())
+    if (x.has_detail()): self.set_detail(x.detail())
+
+  def Equals(self, x):
+    if x is self: return 1
+    if self.has_code_ != x.has_code_: return 0
+    if self.has_code_ and self.code_ != x.code_: return 0
+    if self.has_detail_ != x.has_detail_: return 0
+    if self.has_detail_ and self.detail_ != x.detail_: return 0
+    return 1
+
+  def IsInitialized(self, debug_strs=None):
+    initialized = 1
+    if (not self.has_code_):
+      initialized = 0
+      if debug_strs is not None:
+        debug_strs.append('Required field: code not set.')
+    return initialized
+
+  def ByteSize(self):
+    n = 0
+    n += self.lengthVarInt64(self.code_)
+    if (self.has_detail_): n += 1 + self.lengthString(len(self.detail_))
+    return n + 1
+
+  def ByteSizePartial(self):
+    n = 0
+    if (self.has_code_):
+      n += 1
+      n += self.lengthVarInt64(self.code_)
+    if (self.has_detail_): n += 1 + self.lengthString(len(self.detail_))
+    return n
+
+  def Clear(self):
+    self.clear_code()
+    self.clear_detail()
+
+  def OutputUnchecked(self, out):
+    out.putVarInt32(8)
+    out.putVarInt32(self.code_)
+    if (self.has_detail_):
+      out.putVarInt32(18)
+      out.putPrefixedString(self.detail_)
+
+  def OutputPartial(self, out):
+    if (self.has_code_):
+      out.putVarInt32(8)
+      out.putVarInt32(self.code_)
+    if (self.has_detail_):
+      out.putVarInt32(18)
+      out.putPrefixedString(self.detail_)
+
+  def TryMerge(self, d):
+    while d.avail() > 0:
+      tt = d.getVarInt32()
+      if tt == 8:
+        self.set_code(d.getVarInt32())
+        continue
+      if tt == 18:
+        self.set_detail(d.getPrefixedString())
+        continue
+
+
+      if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError
+      d.skipData(tt)
+
+
+  def __str__(self, prefix="", printElemNumber=0):
+    res=""
+    if self.has_code_: res+=prefix+("code: %s\n" % self.DebugFormatInt32(self.code_))
+    if self.has_detail_: res+=prefix+("detail: %s\n" % self.DebugFormatString(self.detail_))
+    return res
+
+
+  def _BuildTagLookupTable(sparse, maxtag, default=None):
+    return tuple([sparse.get(i, default) for i in xrange(0, 1+maxtag)])
+
+  kcode = 1
+  kdetail = 2
+
+  _TEXT = _BuildTagLookupTable({
+    0: "ErrorCode",
+    1: "code",
+    2: "detail",
+  }, 2)
+
+  _TYPES = _BuildTagLookupTable({
+    0: ProtocolBuffer.Encoder.NUMERIC,
+    1: ProtocolBuffer.Encoder.NUMERIC,
+    2: ProtocolBuffer.Encoder.STRING,
+  }, 2, ProtocolBuffer.Encoder.MAX_TYPE)
+
+
+  _STYLE = """"""
+  _STYLE_CONTENT_TYPE = """"""
+  _PROTO_DESCRIPTOR_NAME = 'apphosting.ext.remote_api.RpcError'
 class Response(ProtocolBuffer.ProtocolMessage):
   has_response_ = 0
   response_ = ""
@@ -395,6 +564,8 @@
   application_error_ = None
   has_java_exception_ = 0
   java_exception_ = ""
+  has_rpc_error_ = 0
+  rpc_error_ = None
 
   def __init__(self, contents=None):
     self.lazy_init_lock_ = thread.allocate_lock()
@@ -458,6 +629,25 @@
 
   def has_java_exception(self): return self.has_java_exception_
 
+  def rpc_error(self):
+    if self.rpc_error_ is None:
+      self.lazy_init_lock_.acquire()
+      try:
+        if self.rpc_error_ is None: self.rpc_error_ = RpcError()
+      finally:
+        self.lazy_init_lock_.release()
+    return self.rpc_error_
+
+  def mutable_rpc_error(self): self.has_rpc_error_ = 1; return self.rpc_error()
+
+  def clear_rpc_error(self):
+
+    if self.has_rpc_error_:
+      self.has_rpc_error_ = 0;
+      if self.rpc_error_ is not None: self.rpc_error_.Clear()
+
+  def has_rpc_error(self): return self.has_rpc_error_
+
 
   def MergeFrom(self, x):
     assert x is not self
@@ -465,6 +655,7 @@
     if (x.has_exception()): self.set_exception(x.exception())
     if (x.has_application_error()): self.mutable_application_error().MergeFrom(x.application_error())
     if (x.has_java_exception()): self.set_java_exception(x.java_exception())
+    if (x.has_rpc_error()): self.mutable_rpc_error().MergeFrom(x.rpc_error())
 
   def Equals(self, x):
     if x is self: return 1
@@ -476,11 +667,14 @@
     if self.has_application_error_ and self.application_error_ != x.application_error_: return 0
     if self.has_java_exception_ != x.has_java_exception_: return 0
     if self.has_java_exception_ and self.java_exception_ != x.java_exception_: return 0
+    if self.has_rpc_error_ != x.has_rpc_error_: return 0
+    if self.has_rpc_error_ and self.rpc_error_ != x.rpc_error_: return 0
     return 1
 
   def IsInitialized(self, debug_strs=None):
     initialized = 1
     if (self.has_application_error_ and not self.application_error_.IsInitialized(debug_strs)): initialized = 0
+    if (self.has_rpc_error_ and not self.rpc_error_.IsInitialized(debug_strs)): initialized = 0
     return initialized
 
   def ByteSize(self):
@@ -489,6 +683,7 @@
     if (self.has_exception_): n += 1 + self.lengthString(len(self.exception_))
     if (self.has_application_error_): n += 1 + self.lengthString(self.application_error_.ByteSize())
     if (self.has_java_exception_): n += 1 + self.lengthString(len(self.java_exception_))
+    if (self.has_rpc_error_): n += 1 + self.lengthString(self.rpc_error_.ByteSize())
     return n
 
   def ByteSizePartial(self):
@@ -497,6 +692,7 @@
     if (self.has_exception_): n += 1 + self.lengthString(len(self.exception_))
     if (self.has_application_error_): n += 1 + self.lengthString(self.application_error_.ByteSizePartial())
     if (self.has_java_exception_): n += 1 + self.lengthString(len(self.java_exception_))
+    if (self.has_rpc_error_): n += 1 + self.lengthString(self.rpc_error_.ByteSizePartial())
     return n
 
   def Clear(self):
@@ -504,6 +700,7 @@
     self.clear_exception()
     self.clear_application_error()
     self.clear_java_exception()
+    self.clear_rpc_error()
 
   def OutputUnchecked(self, out):
     if (self.has_response_):
@@ -519,6 +716,10 @@
     if (self.has_java_exception_):
       out.putVarInt32(34)
       out.putPrefixedString(self.java_exception_)
+    if (self.has_rpc_error_):
+      out.putVarInt32(42)
+      out.putVarInt32(self.rpc_error_.ByteSize())
+      self.rpc_error_.OutputUnchecked(out)
 
   def OutputPartial(self, out):
     if (self.has_response_):
@@ -534,6 +735,10 @@
     if (self.has_java_exception_):
       out.putVarInt32(34)
       out.putPrefixedString(self.java_exception_)
+    if (self.has_rpc_error_):
+      out.putVarInt32(42)
+      out.putVarInt32(self.rpc_error_.ByteSizePartial())
+      self.rpc_error_.OutputPartial(out)
 
   def TryMerge(self, d):
     while d.avail() > 0:
@@ -553,6 +758,12 @@
       if tt == 34:
         self.set_java_exception(d.getPrefixedString())
         continue
+      if tt == 42:
+        length = d.getVarInt32()
+        tmp = ProtocolBuffer.Decoder(d.buffer(), d.pos(), d.pos() + length)
+        d.skip(length)
+        self.mutable_rpc_error().TryMerge(tmp)
+        continue
 
 
       if (tt == 0): raise ProtocolBuffer.ProtocolBufferDecodeError
@@ -568,6 +779,10 @@
       res+=self.application_error_.__str__(prefix + "  ", printElemNumber)
       res+=prefix+">\n"
     if self.has_java_exception_: res+=prefix+("java_exception: %s\n" % self.DebugFormatString(self.java_exception_))
+    if self.has_rpc_error_:
+      res+=prefix+"rpc_error <\n"
+      res+=self.rpc_error_.__str__(prefix + "  ", printElemNumber)
+      res+=prefix+">\n"
     return res
 
 
@@ -578,6 +793,7 @@
   kexception = 2
   kapplication_error = 3
   kjava_exception = 4
+  krpc_error = 5
 
   _TEXT = _BuildTagLookupTable({
     0: "ErrorCode",
@@ -585,7 +801,8 @@
     2: "exception",
     3: "application_error",
     4: "java_exception",
-  }, 4)
+    5: "rpc_error",
+  }, 5)
 
   _TYPES = _BuildTagLookupTable({
     0: ProtocolBuffer.Encoder.NUMERIC,
@@ -593,7 +810,8 @@
     2: ProtocolBuffer.Encoder.STRING,
     3: ProtocolBuffer.Encoder.STRING,
     4: ProtocolBuffer.Encoder.STRING,
-  }, 4, ProtocolBuffer.Encoder.MAX_TYPE)
+    5: ProtocolBuffer.Encoder.STRING,
+  }, 5, ProtocolBuffer.Encoder.MAX_TYPE)
 
 
   _STYLE = """"""
@@ -1165,4 +1383,4 @@
 if _extension_runtime:
   pass
 
-__all__ = ['Request','ApplicationError','Response','TransactionRequest','TransactionRequest_Precondition','TransactionQueryResult']
+__all__ = ['Request','ApplicationError','RpcError','Response','TransactionRequest','TransactionRequest_Precondition','TransactionQueryResult']
diff --git a/google/appengine/tools/appcfg.py b/google/appengine/tools/appcfg.py
index 79e522e..5f4bffe 100644
--- a/google/appengine/tools/appcfg.py
+++ b/google/appengine/tools/appcfg.py
@@ -87,9 +87,10 @@
 LIST_DELIMITER = '\n'
 TUPLE_DELIMITER = '|'
 BACKENDS_ACTION = 'backends'
-BACKENDS_MESSAGE = ('Looks like you\'re using Backends. We suggest that you '
-                    'start looking at App Engine Modules. See the Modules '
-                    'documentation to learn more about converting: ')
+BACKENDS_MESSAGE = ('Warning: This application uses Backends, a deprecated '
+                    'feature that has been replaced by Modules, which '
+                    'offers additional functionality. Please convert your '
+                    'backends to modules as described at: ')
 _CONVERTING_URL = (
     'https://developers.google.com/appengine/docs/%s/modules/converting')
 
@@ -157,6 +158,11 @@
 APP_YAML_FILENAME = 'app.yaml'
 
 
+
+
+GO_APP_BUILDER = os.path.join('goroot', 'bin', 'go-app-builder')
+
+
 class Error(Exception):
   pass
 
@@ -171,29 +177,31 @@
   pass
 
 
-def PrintUpdate(msg):
-  """Print a message to stderr.
+def PrintUpdate(msg, error_fh=sys.stderr):
+  """Print a message to stderr or the given file-like object.
 
   If 'verbosity' is greater than 0, print the message.
 
   Args:
     msg: The string to print.
+    error_fh: Where to send the message.
   """
   if verbosity > 0:
     timestamp = datetime.datetime.now()
-    print >>sys.stderr, '%s %s' % (timestamp.strftime('%I:%M %p'), msg)
+    print >>error_fh, '%s %s' % (timestamp.strftime('%I:%M %p'), msg)
 
 
-def StatusUpdate(msg):
-  """Print a status message to stderr."""
-  PrintUpdate(msg)
+def StatusUpdate(msg, error_fh=sys.stderr):
+  """Print a status message to stderr or the given file-like object."""
+  PrintUpdate(msg, error_fh)
 
 
-def BackendsStatusUpdate(runtime):
+def BackendsStatusUpdate(runtime, error_fh=sys.stderr):
   """Print the Backends status message based on current runtime.
 
   Args:
     runtime: String name of current runtime.
+    error_fh: Where to send the message.
   """
   language = runtime
   if language == 'python27':
@@ -201,12 +209,12 @@
   elif language == 'java7':
     language = 'java'
   if language == 'python' or language == 'java':
-    StatusUpdate(BACKENDS_MESSAGE + (_CONVERTING_URL % language))
+    StatusUpdate(BACKENDS_MESSAGE + (_CONVERTING_URL % language), error_fh)
 
 
-def ErrorUpdate(msg):
+def ErrorUpdate(msg, error_fh=sys.stderr):
   """Print an error message to stderr."""
-  PrintUpdate(msg)
+  PrintUpdate(msg, error_fh)
 
 
 def _PrintErrorAndExit(stream, msg, exit_code=2):
@@ -247,21 +255,22 @@
   as a container for various metadata about the file.
   """
 
-  def __init__(self, config, filename):
+  def __init__(self, config, filename, error_fh=sys.stderr):
     """Initializes a FileClassification instance.
 
     Args:
       config: The app.yaml object to check the filename against.
       filename: The name of the file.
+      error_fh: Where to send status and error messages.
     """
+    self.__error_fh = error_fh
     self.__static_mime_type = self.__GetMimeTypeIfStaticFile(config, filename)
     self.__static_app_readable = self.__GetAppReadableIfStaticFile(config,
                                                                    filename)
     self.__error_mime_type, self.__error_code = self.__LookupErrorBlob(config,
                                                                        filename)
 
-  @staticmethod
-  def __GetMimeTypeIfStaticFile(config, filename):
+  def __GetMimeTypeIfStaticFile(self, config, filename):
     """Looks up the mime type for 'filename'.
 
     Uses the handlers in 'config' to determine if the file should
@@ -275,8 +284,8 @@
       The mime type string.  For example, 'text/plain' or 'image/gif'.
       None if this is not a static file.
     """
-    if FileClassification.__FileNameImpliesStaticFile(filename):
-      return FileClassification.__MimeType(filename)
+    if self.__FileNameImpliesStaticFile(filename):
+      return self.__MimeType(filename)
     for handler in config.handlers:
       handler_type = handler.GetHandlerType()
       if handler_type in ('static_dir', 'static_files'):
@@ -285,7 +294,7 @@
         else:
           regex = handler.upload
         if re.match(regex, filename):
-          return handler.mime_type or FileClassification.__MimeType(filename)
+          return handler.mime_type or self.__MimeType(filename)
     return None
 
   @staticmethod
@@ -333,8 +342,7 @@
           return handler.application_readable
     return False
 
-  @staticmethod
-  def __LookupErrorBlob(config, filename):
+  def __LookupErrorBlob(self, config, filename):
     """Looks up the mime type and error_code for 'filename'.
 
     Uses the error handlers in 'config' to determine if the file should
@@ -359,15 +367,14 @@
         if error_handler.mime_type:
           return (error_handler.mime_type, error_code)
         else:
-          return (FileClassification.__MimeType(filename), error_code)
+          return (self.__MimeType(filename), error_code)
     return (None, None)
 
-  @staticmethod
-  def __MimeType(filename, default='application/octet-stream'):
+  def __MimeType(self, filename, default='application/octet-stream'):
     guess = mimetypes.guess_type(filename)[0]
     if guess is None:
-      print >>sys.stderr, ('Could not guess mimetype for %s.  Using %s.'
-                           % (filename, default))
+      print >>self.__error_fh, ('Could not guess mimetype for %s.  Using %s.'
+                                % (filename, default))
       return default
     return guess
 
@@ -409,7 +416,7 @@
   return LIST_DELIMITER.join(file_list)
 
 
-def GetRemoteResourceLimits(rpcserver, config):
+def GetRemoteResourceLimits(rpcserver, config, error_fh=sys.stderr):
   """Get the resource limit as reported by the admin console.
 
   Get the resource limits by querying the admin_console/appserver. The
@@ -419,12 +426,13 @@
   Args:
     rpcserver: The RPC server to use.
     config: The appyaml configuration.
+    error_fh: Where to send status and error messages.
 
   Returns:
     A dictionary.
   """
   try:
-    StatusUpdate('Getting current resource limits.')
+    StatusUpdate('Getting current resource limits.', error_fh)
     yaml_data = rpcserver.Send('/api/appversion/getresourcelimits',
                                app_id=config.application,
                                version=config.version)
@@ -440,7 +448,7 @@
   return yaml.safe_load(yaml_data)
 
 
-def GetResourceLimits(rpcserver, config):
+def GetResourceLimits(rpcserver, config, error_fh=sys.stderr):
   """Gets the resource limits.
 
   Gets the resource limits that should be applied to apps. Any values
@@ -451,12 +459,13 @@
   Args:
     rpcserver: The RPC server to use.
     config: The appyaml configuration.
+    error_fh: Where to send status and error messages.
 
   Returns:
     A dictionary.
   """
   resource_limits = DEFAULT_RESOURCE_LIMITS.copy()
-  resource_limits.update(GetRemoteResourceLimits(rpcserver, config))
+  resource_limits.update(GetRemoteResourceLimits(rpcserver, config, error_fh))
   logging.debug('Using resource limits: %s', resource_limits)
   return resource_limits
 
@@ -528,20 +537,22 @@
 class IndexDefinitionUpload(object):
   """Provides facilities to upload index definitions to the hosting service."""
 
-  def __init__(self, rpcserver, definitions):
+  def __init__(self, rpcserver, definitions, error_fh=sys.stderr):
     """Creates a new DatastoreIndexUpload.
 
     Args:
       rpcserver: The RPC server to use.  Should be an instance of HttpRpcServer
         or TestRpcServer.
       definitions: An IndexDefinitions object.
+      error_fh: Where to send status and error messages.
     """
     self.rpcserver = rpcserver
     self.definitions = definitions
+    self.error_fh = error_fh
 
   def DoUpload(self):
     """Uploads the index definitions."""
-    StatusUpdate('Uploading index definitions.')
+    StatusUpdate('Uploading index definitions.', self.error_fh)
 
     with TempChangeField(self.definitions, 'application', None) as app_id:
       self.rpcserver.Send('/api/datastore/index/add',
@@ -552,20 +563,22 @@
 class CronEntryUpload(object):
   """Provides facilities to upload cron entries to the hosting service."""
 
-  def __init__(self, rpcserver, cron):
+  def __init__(self, rpcserver, cron, error_fh=sys.stderr):
     """Creates a new CronEntryUpload.
 
     Args:
       rpcserver: The RPC server to use.  Should be an instance of a subclass of
       AbstractRpcServer
       cron: The CronInfoExternal object loaded from the cron.yaml file.
+      error_fh: Where to send status and error messages.
     """
     self.rpcserver = rpcserver
     self.cron = cron
+    self.error_fh = error_fh
 
   def DoUpload(self):
     """Uploads the cron entries."""
-    StatusUpdate('Uploading cron entries.')
+    StatusUpdate('Uploading cron entries.', self.error_fh)
 
     with TempChangeField(self.cron, 'application', None) as app_id:
       self.rpcserver.Send('/api/cron/update',
@@ -576,20 +589,22 @@
 class QueueEntryUpload(object):
   """Provides facilities to upload task queue entries to the hosting service."""
 
-  def __init__(self, rpcserver, queue):
+  def __init__(self, rpcserver, queue, error_fh=sys.stderr):
     """Creates a new QueueEntryUpload.
 
     Args:
       rpcserver: The RPC server to use.  Should be an instance of a subclass of
-      AbstractRpcServer
+        AbstractRpcServer
       queue: The QueueInfoExternal object loaded from the queue.yaml file.
+      error_fh: Where to send status and error messages.
     """
     self.rpcserver = rpcserver
     self.queue = queue
+    self.error_fh = error_fh
 
   def DoUpload(self):
     """Uploads the task queue entries."""
-    StatusUpdate('Uploading task queue entries.')
+    StatusUpdate('Uploading task queue entries.', self.error_fh)
 
     with TempChangeField(self.queue, 'application', None) as app_id:
       self.rpcserver.Send('/api/queue/update',
@@ -597,23 +612,50 @@
                           payload=self.queue.ToYAML())
 
 
+class DispatchEntryUpload(object):
+  """Provides facilities to upload dispatch entries to the hosting service."""
+
+  def __init__(self, rpcserver, dispatch, error_fh=sys.stderr):
+    """Creates a new DispatchEntryUpload.
+
+    Args:
+      rpcserver: The RPC server to use.  Should be an instance of a subclass of
+        AbstractRpcServer
+      dispatch: The DispatchInfoExternal object loaded from the dispatch.yaml
+        file.
+      error_fh: Where to send status and error messages.
+    """
+    self.rpcserver = rpcserver
+    self.dispatch = dispatch
+    self.error_fh = error_fh
+
+  def DoUpload(self):
+    """Uploads the dispatch entries."""
+    StatusUpdate('Uploading dispatch entries.', self.error_fh)
+    self.rpcserver.Send('/api/dispatch/update',
+                        app_id=self.dispatch.application,
+                        payload=self.dispatch.ToYAML())
+
+
 class DosEntryUpload(object):
   """Provides facilities to upload dos entries to the hosting service."""
 
-  def __init__(self, rpcserver, dos):
+  def __init__(self, rpcserver, dos, error_fh=sys.stderr):
     """Creates a new DosEntryUpload.
 
     Args:
       rpcserver: The RPC server to use. Should be an instance of a subclass of
         AbstractRpcServer.
       dos: The DosInfoExternal object loaded from the dos.yaml file.
+      error_fh: Where to send status and error messages.
     """
     self.rpcserver = rpcserver
     self.dos = dos
+    self.error_fh = error_fh
 
   def DoUpload(self):
     """Uploads the dos entries."""
-    StatusUpdate('Uploading DOS entries.')
+    StatusUpdate('Uploading DOS entries.', self.error_fh)
 
     with TempChangeField(self.dos, 'application', None) as app_id:
       self.rpcserver.Send('/api/dos/update',
@@ -624,7 +666,7 @@
 class PagespeedEntryUpload(object):
   """Provides facilities to upload pagespeed configs to the hosting service."""
 
-  def __init__(self, rpcserver, config, pagespeed):
+  def __init__(self, rpcserver, config, pagespeed, error_fh=sys.stderr):
     """Creates a new PagespeedEntryUpload.
 
     Args:
@@ -632,17 +674,19 @@
         AbstractRpcServer.
       config: The AppInfoExternal object derived from the app.yaml file.
       pagespeed: The PagespeedEntry object from config.
+      error_fh: Where to send status and error messages.
     """
     self.rpcserver = rpcserver
     self.config = config
     self.pagespeed = pagespeed
+    self.error_fh = error_fh
 
   def DoUpload(self):
     """Uploads the pagespeed entries."""
 
     pagespeed_yaml = ''
     if self.pagespeed:
-      StatusUpdate('Uploading PageSpeed configuration.')
+      StatusUpdate('Uploading PageSpeed configuration.', self.error_fh)
       pagespeed_yaml = self.pagespeed.ToYAML()
     try:
       self.rpcserver.Send('/api/appversion/updatepagespeed',
@@ -666,7 +710,7 @@
 class DefaultVersionSet(object):
   """Provides facilities to set the default (serving) version."""
 
-  def __init__(self, rpcserver, app_id, module, version):
+  def __init__(self, rpcserver, app_id, module, version, error_fh=sys.stderr):
     """Creates a new DefaultVersionSet.
 
     Args:
@@ -675,11 +719,13 @@
       app_id: The application to make the change to.
       module: The module to set the default version of (if any).
       version: The version to set as the default.
+      error_fh: Where to send status and error messages.
     """
     self.rpcserver = rpcserver
     self.app_id = app_id
     self.module = module
     self.version = version
+    self.error_fh = error_fh
 
   def SetVersion(self):
     """Sets the default version."""
@@ -690,7 +736,8 @@
         StatusUpdate('Setting the default version of modules %s of application '
                      '%s to %s.' % (', '.join(modules),
                                     self.app_id,
-                                    self.version))
+                                    self.version),
+                     self.error_fh)
 
 
 
@@ -703,10 +750,11 @@
 
       else:
         StatusUpdate('Setting default version of module %s of application %s '
-                     'to %s.' % (self.module, self.app_id, self.version))
+                     'to %s.' % (self.module, self.app_id, self.version),
+                     self.error_fh)
     else:
       StatusUpdate('Setting default version of application %s to %s.'
-                   % (self.app_id, self.version))
+                   % (self.app_id, self.version), self.error_fh)
     self.rpcserver.Send('/api/appversion/setdefault',
                         app_id=self.app_id,
                         module=self.module,
@@ -716,24 +764,25 @@
 class TrafficMigrator(object):
   """Provides facilities to migrate traffic."""
 
-  def __init__(self, rpcserver, app_id, version):
+  def __init__(self, rpcserver, app_id, version, error_fh=sys.stderr):
     """Creates a new TrafficMigrator.
 
     Args:
       rpcserver: The RPC server to use. Should be an instance of a subclass of
         AbstractRpcServer.
       app_id: The application to make the change to.
-
       version: The version to set as the default.
+      error_fh: Where to send status and error messages.
     """
     self.rpcserver = rpcserver
     self.app_id = app_id
     self.version = version
+    self.error_fh = error_fh
 
   def MigrateTraffic(self):
     """Migrates traffic."""
     StatusUpdate('Migrating traffic of application %s to %s.'
-                 % (self.app_id, self.version))
+                 % (self.app_id, self.version), self.error_fh)
     self.rpcserver.Send('/api/appversion/migratetraffic',
                         app_id=self.app_id,
                         version=self.version)
@@ -742,14 +791,16 @@
 class IndexOperation(object):
   """Provide facilities for writing Index operation commands."""
 
-  def __init__(self, rpcserver):
+  def __init__(self, rpcserver, error_fh=sys.stderr):
     """Creates a new IndexOperation.
 
     Args:
       rpcserver: The RPC server to use.  Should be an instance of HttpRpcServer
         or TestRpcServer.
+      error_fh: Where to send status and error messages.
     """
     self.rpcserver = rpcserver
+    self.error_fh = error_fh
 
   def DoDiff(self, definitions):
     """Retrieve diff file from the server.
@@ -765,7 +816,7 @@
       present on the server but missing from the index.yaml file (indicating
       that these indexes should probably be vacuumed).
     """
-    StatusUpdate('Fetching index definitions diff.')
+    StatusUpdate('Fetching index definitions diff.', self.error_fh)
     with TempChangeField(definitions, 'application', None) as app_id:
       response = self.rpcserver.Send('/api/datastore/index/diff',
                                      app_id=app_id,
@@ -786,7 +837,7 @@
       be normal behavior as there is a potential race condition between fetching
       the index-diff and sending deletion confirmation through.
     """
-    StatusUpdate('Deleting selected index definitions.')
+    StatusUpdate('Deleting selected index definitions.', self.error_fh)
 
     response = self.rpcserver.Send('/api/datastore/index/delete',
                                    app_id=app_id,
@@ -797,7 +848,8 @@
 class VacuumIndexesOperation(IndexOperation):
   """Provide facilities to request the deletion of datastore indexes."""
 
-  def __init__(self, rpcserver, force, confirmation_fn=raw_input):
+  def __init__(self, rpcserver, force, confirmation_fn=raw_input,
+               error_fh=sys.stderr):
     """Creates a new VacuumIndexesOperation.
 
     Args:
@@ -805,8 +857,9 @@
         or TestRpcServer.
       force: True to force deletion of indexes, else False.
       confirmation_fn: Function used for getting input form user.
+      error_fh: Where to send status and error messages.
     """
-    super(VacuumIndexesOperation, self).__init__(rpcserver)
+    super(VacuumIndexesOperation, self).__init__(rpcserver, error_fh)
     self.force = force
     self.confirmation_fn = confirmation_fn
 
@@ -917,7 +970,8 @@
                vhost,
                include_vhost,
                include_all=None,
-               time_func=time.time):
+               time_func=time.time,
+               error_fh=sys.stderr):
     """Constructor.
 
     Args:
@@ -937,6 +991,7 @@
         about the request.
       time_func: A time.time() compatible function, which can be overridden for
         testing.
+      error_fh: Where to send status and error messages.
     """
 
     self.rpcserver = rpcserver
@@ -948,6 +1003,7 @@
     self.vhost = vhost
     self.include_vhost = include_vhost
     self.include_all = include_all
+    self.error_fh = error_fh
 
     self.module = module
     self.version_id = version_id
@@ -980,10 +1036,10 @@
     """
     if self.module:
       StatusUpdate('Downloading request logs for app %s module %s version %s.' %
-                   (self.app_id, self.module, self.version_id))
+                   (self.app_id, self.module, self.version_id), self.error_fh)
     else:
       StatusUpdate('Downloading request logs for app %s version %s.' %
-                   (self.app_id, self.version_id))
+                   (self.app_id, self.version_id), self.error_fh)
 
 
 
@@ -999,9 +1055,11 @@
             break
           last_offset = new_offset
         except KeyboardInterrupt:
-          StatusUpdate('Keyboard interrupt; saving data downloaded so far.')
+          StatusUpdate('Keyboard interrupt; saving data downloaded so far.',
+                       self.error_fh)
           break
-      StatusUpdate('Copying request logs to %r.' % self.output_file)
+      StatusUpdate('Copying request logs to %r.' % self.output_file,
+                   self.error_fh)
       if self.output_file == '-':
         of = sys.stdout
       else:
@@ -1018,7 +1076,7 @@
           of.close()
     finally:
       tf.close()
-    StatusUpdate('Copied %d records.' % line_count)
+    StatusUpdate('Copied %d records.' % line_count, self.error_fh)
 
   def RequestLogLines(self, tf, offset):
     """Make a single roundtrip to the server.
@@ -1217,12 +1275,13 @@
   return line_count
 
 
-def FindSentinel(filename, blocksize=2**16):
+def FindSentinel(filename, blocksize=2**16, error_fh=sys.stderr):
   """Return the sentinel line from the output file.
 
   Args:
     filename: The filename of the output file.  (We'll read this file.)
     blocksize: Optional block size for buffering, for unit testing.
+    error_fh: Where to send status and error messages.
 
   Returns:
     The contents of the last line in the file that doesn't start with
@@ -1231,12 +1290,14 @@
     the last 'blocksize' bytes of the file.
   """
   if filename == '-':
-    StatusUpdate('Can\'t combine --append with output to stdout.')
+    StatusUpdate('Can\'t combine --append with output to stdout.',
+                 error_fh)
     sys.exit(2)
   try:
     fp = open(filename, 'rb')
   except IOError, err:
-    StatusUpdate('Append mode disabled: can\'t read %r: %s.' % (filename, err))
+    StatusUpdate('Append mode disabled: can\'t read %r: %s.' % (filename, err),
+                 error_fh)
     return None
   try:
     fp.seek(0, 2)
@@ -1250,7 +1311,7 @@
     if not sentinel:
 
       StatusUpdate('Append mode disabled: can\'t find sentinel in %r.' %
-                   filename)
+                   filename, error_fh)
       return None
     return sentinel.rstrip('\n')
   finally:
@@ -1455,7 +1516,8 @@
       raise
 
 
-def DoDownloadApp(rpcserver, out_dir, app_id, module, app_version):
+def DoDownloadApp(rpcserver, out_dir, app_id, module, app_version,
+                  error_fh=sys.stderr):
   """Downloads the files associated with a particular app version.
 
   Args:
@@ -1469,9 +1531,10 @@
       - None: We'll download the latest default version.
       - <major>: We'll download the latest minor version.
       - <major>/<minor>: We'll download that exact version.
+    error_fh: Where to send status and error messages.
   """
 
-  StatusUpdate('Fetching file list...')
+  StatusUpdate('Fetching file list...', error_fh)
 
   url_args = {'app_id': app_id}
   if module:
@@ -1481,7 +1544,7 @@
 
   result = rpcserver.Send('/api/files/list', **url_args)
 
-  StatusUpdate('Fetching files...')
+  StatusUpdate('Fetching files...', error_fh)
 
   lines = result.splitlines()
 
@@ -1514,7 +1577,8 @@
                     '"%s"', size_str)
       return
 
-    StatusUpdate('[%d/%d] %s' % (current_file_number, num_files, path))
+    StatusUpdate('[%d/%d] %s' % (current_file_number, num_files, path),
+                 error_fh)
 
     def TryGet():
       """A request to /api/files/get which works with the RetryWithBackoff."""
@@ -1531,7 +1595,8 @@
           raise
 
     def PrintRetryMessage(_, delay):
-      StatusUpdate('Server busy.  Will try again in %d seconds.' % delay)
+      StatusUpdate('Server busy.  Will try again in %d seconds.' % delay,
+                   error_fh)
 
     success, contents = RetryWithBackoff(TryGet, PrintRetryMessage)
     if not success:
@@ -1743,7 +1808,7 @@
     if result:
       warnings = result.get('warnings')
       for warning in warnings:
-        StatusUpdate('WARNING: %s' % warning)
+        StatusUpdate('WARNING: %s' % warning, self.error_fh)
 
     self.in_transaction = True
 
@@ -1751,7 +1816,8 @@
     blobs_to_clone = []
     errorblobs = {}
     for path, content_hash in self.files.iteritems():
-      file_classification = FileClassification(self.config, path)
+      file_classification = FileClassification(
+          self.config, path, error_fh=self.error_fh)
 
       if file_classification.IsStaticFile():
         upload_path = path
@@ -1785,12 +1851,13 @@
         return
 
       StatusUpdate('Cloning %d %s file%s.' %
-                   (len(files), file_type, len(files) != 1 and 's' or ''))
+                   (len(files), file_type, len(files) != 1 and 's' or ''),
+                   self.error_fh)
 
       max_files = self.resource_limits['max_files_to_clone']
       for i in xrange(0, len(files), max_files):
         if i > 0 and i % max_files == 0:
-          StatusUpdate('Cloned %d files.' % i)
+          StatusUpdate('Cloned %d files.' % i, self.error_fh)
 
         chunk = files[i:min(len(files), i + max_files)]
         result = self.Send(url, payload=BuildClonePostBody(chunk))
@@ -1834,7 +1901,8 @@
 
     del self.files[path]
 
-    file_classification = FileClassification(self.config, path)
+    file_classification = FileClassification(
+        self.config, path, error_fh=self.error_fh)
     payload = file_handle.read()
     if file_classification.IsStaticFile():
       upload_path = path
@@ -1859,10 +1927,10 @@
   def Precompile(self):
     """Handle precompilation."""
 
-    StatusUpdate('Compilation starting.')
+    StatusUpdate('Compilation starting.', self.error_fh)
 
     files = []
-    if self.config.runtime == 'go':
+    if self.config.GetEffectiveRuntime() == 'go':
 
 
       for f in self.all_files:
@@ -1871,11 +1939,11 @@
 
     while True:
       if files:
-        StatusUpdate('Compilation: %d files left.' % len(files))
+        StatusUpdate('Compilation: %d files left.' % len(files), self.error_fh)
       files = self.PrecompileBatch(files)
       if not files:
         break
-    StatusUpdate('Compilation completed.')
+    StatusUpdate('Compilation completed.', self.error_fh)
 
   def PrecompileBatch(self, files):
     """Precompile a batch of files.
@@ -1915,7 +1983,7 @@
       raise RuntimeError('Not all required files have been uploaded.')
 
     def PrintRetryMessage(_, delay):
-      StatusUpdate('Will check again in %s seconds.' % delay)
+      StatusUpdate('Will check again in %s seconds.' % delay, self.error_fh)
 
     app_summary = self.Deploy()
 
@@ -1951,9 +2019,12 @@
             lambda: (self.IsEndpointsConfigUpdated(), None),
             PrintRetryMessage, 1, 2, 60, 20)
         if not success:
-          logging.warning('Failed to update Endpoints configuration.  Try '
-                          'updating again.')
-          raise RuntimeError('Endpoints config update failed.')
+          error_message = ('Failed to update Endpoints configuration.  Check '
+                           'the app\'s AppEngine logs for errors: %s' %
+                           self.GetLogUrl())
+          StatusUpdate(error_message, self.error_fh)
+          logging.warning(error_message)
+          raise RuntimeError(error_message)
       self.in_transaction = False
 
     return app_summary
@@ -1975,7 +2046,7 @@
     if self.files:
       raise RuntimeError('Not all required files have been uploaded.')
 
-    StatusUpdate('Starting deployment.')
+    StatusUpdate('Starting deployment.', self.error_fh)
     result = self.Send('/api/appversion/deploy')
     self.deployed = True
 
@@ -1995,7 +2066,7 @@
     """
     assert self.deployed, 'Deploy() must be called before IsReady().'
 
-    StatusUpdate('Checking if deployment succeeded.')
+    StatusUpdate('Checking if deployment succeeded.', self.error_fh)
     result = self.Send('/api/appversion/isready')
     return result == '1'
 
@@ -2010,7 +2081,7 @@
     """
     assert self.deployed, 'Deploy() must be called before StartServing().'
 
-    StatusUpdate('Deployment successful.')
+    StatusUpdate('Deployment successful.', self.error_fh)
     self.params['willcheckserving'] = '1'
     result = self.Send('/api/appversion/startserving')
     del self.params['willcheckserving']
@@ -2048,7 +2119,7 @@
     """
     assert self.started, 'StartServing() must be called before IsServing().'
 
-    StatusUpdate('Checking if updated app version is serving.')
+    StatusUpdate('Checking if updated app version is serving.', self.error_fh)
 
     self.params['new_serving_resp'] = '1'
     result = self.Send('/api/appversion/isserving')
@@ -2062,7 +2133,7 @@
     message = result.get('message')
     fatal = result.get('fatal')
     if message:
-      StatusUpdate(message)
+      StatusUpdate(message, self.error_fh)
     if fatal:
       raise CannotStartServingError(message or 'Unknown error.')
     return result['serving'], result
@@ -2083,6 +2154,13 @@
       return None
     return response_dict
 
+  def GetLogUrl(self):
+    """Get the URL for the app's logs."""
+    module = '%s:' % self.module if self.module else ''
+    return ('https://appengine.google.com/logs?' +
+            urllib.urlencode((('app_id', self.app_id),
+                              ('version_id', module + self.version))))
+
   def IsEndpointsConfigUpdated(self):
     """Check if the Endpoints configuration for this app has been updated.
 
@@ -2103,7 +2181,8 @@
     assert self.started, ('StartServing() must be called before '
                           'IsEndpointsConfigUpdated().')
 
-    StatusUpdate('Checking if Endpoints configuration has been updated.')
+    StatusUpdate('Checking if Endpoints configuration has been updated.',
+                 self.error_fh)
 
     result = self.Send('/api/isconfigupdated')
     result = AppVersionUpload._ValidateIsEndpointsConfigUpdatedYaml(result)
@@ -2116,7 +2195,7 @@
     """Rolls back the transaction if one is in progress."""
     if not self.in_transaction:
       return
-    StatusUpdate('Rolling back the update.')
+    StatusUpdate('Rolling back the update.', self.error_fh)
     self.Send('/api/appversion/rollback')
     self.in_transaction = False
     self.files = {}
@@ -2134,12 +2213,13 @@
     """
     logging.info('Reading app configuration.')
 
-    StatusUpdate('\nStarting update of %s' % self.Describe())
+    StatusUpdate('\nStarting update of %s' % self.Describe(), self.error_fh)
 
 
     path = ''
     try:
-      self.resource_limits = GetResourceLimits(self.rpcserver, self.config)
+      self.resource_limits = GetResourceLimits(
+          self.rpcserver, self.config, self.error_fh)
       self._AddFilesThatAreSmallEnough(paths, openfunc)
     except KeyboardInterrupt:
       logging.info('User interrupted. Aborting.')
@@ -2162,7 +2242,7 @@
           ErrorUpdate('Error %d: --- begin server output ---\n'
                       '%s\n--- end server output ---' %
                       (e.code, e.read().rstrip('\n')))
-          if e.code == 422 or self.config.runtime == 'go':
+          if e.code == 422 or self.config.GetEffectiveRuntime() == 'go':
 
 
 
@@ -2177,7 +2257,7 @@
 
 
       app_summary = self.Commit()
-      StatusUpdate('Completed update of %s' % self.Describe())
+      StatusUpdate('Completed update of %s' % self.Describe(), self.error_fh)
 
     except BaseException, e:
       self._LogDoUploadException(e)
@@ -2200,7 +2280,7 @@
       openfunc: A function that takes a paths element, and returns a file-like
         object.
     """
-    StatusUpdate('Scanning files on local disk.')
+    StatusUpdate('Scanning files on local disk.', self.error_fh)
     num_files = 0
     for path in paths:
       file_handle = openfunc(path)
@@ -2208,7 +2288,8 @@
         file_length = GetFileLength(file_handle)
 
 
-        file_classification = FileClassification(self.config, path)
+        file_classification = FileClassification(
+            self.config, path, self.error_fh)
         if file_classification.IsApplicationFile():
           max_size = self.resource_limits['max_file_size']
         else:
@@ -2228,7 +2309,7 @@
 
       num_files += 1
       if num_files % 500 == 0:
-        StatusUpdate('Scanned %d files.' % num_files)
+        StatusUpdate('Scanned %d files.' % num_files, self.error_fh)
 
   def _UploadMissingFiles(self, missing_files, openfunc):
     """DoUpload helper to upload files that need to be uploaded.
@@ -2243,7 +2324,8 @@
     if not missing_files:
       return
 
-    StatusUpdate('Uploading %d files and blobs.' % len(missing_files))
+    StatusUpdate('Uploading %d files and blobs.' % len(missing_files),
+                 self.error_fh)
     num_files = 0
     for missing_file in missing_files:
       file_handle = openfunc(missing_file)
@@ -2256,13 +2338,13 @@
       num_files += 1
       if num_files % 500 == 0:
         StatusUpdate('Processed %d out of %s.' %
-                     (num_files, len(missing_files)))
+                     (num_files, len(missing_files)), self.error_fh)
 
 
     self.file_batcher.Flush()
     self.blob_batcher.Flush()
     self.errorblob_batcher.Flush()
-    StatusUpdate('Uploaded %d files and blobs' % num_files)
+    StatusUpdate('Uploaded %d files and blobs' % num_files, self.error_fh)
 
   @staticmethod
   def _LogDoUploadException(exception):
@@ -2773,7 +2855,8 @@
                       help=('Set the application, overriding the application '
                             'value from app.yaml file.'))
     parser.add_option('-M', '--module', action='store', dest='module',
-                      help=optparse.SUPPRESS_HELP)
+                      help=('Set the module, overriding the module value '
+                            'from app.yaml.'))
     parser.add_option('-V', '--version', action='store', dest='version',
                       help=('Set the (major) version, overriding the version '
                             'value from app.yaml file.'))
@@ -2874,7 +2957,7 @@
 
       return (email, password)
 
-    StatusUpdate('Host: %s' % self.options.server)
+    StatusUpdate('Host: %s' % self.options.server, self.error_fh)
 
     source = GetSourceName()
 
@@ -3019,7 +3102,7 @@
       if self._JavaSupported():
         if appcfg_java.IsWarFileWithoutYaml(basepath):
           java_app_update = appcfg_java.JavaAppUpdate(basepath, self.options)
-          appyaml_string = java_app_update.GenerateAppYamlString(basepath, [])
+          appyaml_string = java_app_update.GenerateAppYamlString([])
           appyaml = appinfo.LoadSingleAppInfo(appyaml_string)
         if not appyaml:
           self.parser.error('Directory contains neither an %s.yaml '
@@ -3039,7 +3122,7 @@
     if self.options.version:
       appyaml.version = self.options.version
     if self.options.runtime:
-      appyaml.runtime = self.options.runtime
+      appinfo.VmSafeSetRuntime(appyaml, self.options.runtime)
     if self.options.env_variables:
       if appyaml.env_variables is None:
         appyaml.env_variables = appinfo.EnvironmentVariables()
@@ -3062,7 +3145,7 @@
       msg += '; version: %s' % appyaml.version
       if appyaml.version != orig_version:
         msg += ' (was: %s)' % orig_version
-    StatusUpdate(msg)
+    StatusUpdate(msg, self.error_fh)
     return appyaml
 
   def _ParseYamlFile(self, basepath, basename, parser):
@@ -3183,17 +3266,25 @@
     self._SetApplication(queue_yaml, 'queue', appyaml)
     return queue_yaml
 
-  def _ParseDispatchYaml(self, basepath):
+  def _ParseDispatchYaml(self, basepath, appyaml=None):
     """Parses the dispatch.yaml file.
 
     Args:
       basepath: the directory of the application.
+      appyaml: The app.yaml, if present.
 
     Returns:
       A DispatchInfoExternal object or None if the file does not exist.
     """
-    return self._ParseYamlFile(basepath, 'dispatch',
-                               dispatchinfo.LoadSingleDispatch)
+    dispatch_yaml = self._ParseYamlFile(basepath,
+                                        'dispatch',
+                                        dispatchinfo.LoadSingleDispatch)
+
+    if not dispatch_yaml:
+      return None
+
+    self._SetApplication(dispatch_yaml, 'dispatch', appyaml)
+    return dispatch_yaml
 
   def _ParseDosYaml(self, basepath, appyaml=None):
     """Parses the dos.yaml file.
@@ -3284,7 +3375,8 @@
       paths to absolute paths, its stderr is raised.
     """
 
-    if not self.options.precompilation and appyaml.runtime == 'go':
+    if (not self.options.precompilation and
+        appyaml.GetEffectiveRuntime() == 'go'):
       logging.warning('Precompilation is required for Go apps; '
                       'ignoring --no_precompilation')
       self.options.precompilation = True
@@ -3301,13 +3393,23 @@
     paths = self.file_iterator(basepath, appyaml.skip_files, appyaml.runtime)
     openfunc = lambda path: self.opener(os.path.join(basepath, path), 'rb')
 
-    if appyaml.runtime == 'go':
+    gopath = os.environ.get('GOPATH')
+    if appyaml.GetEffectiveRuntime() == 'go' and gopath:
 
 
-      goroot = os.path.join(os.path.dirname(google.appengine.__file__),
-                            '../../goroot')
-      gopath = os.environ.get('GOPATH')
-      if os.path.isdir(goroot) and gopath:
+
+
+
+
+
+      sdk_base = os.path.normpath(os.path.join(
+          google.appengine.__file__, '..', '..', '..'))
+      goroot = os.path.join(sdk_base, 'goroot')
+      if not os.path.exists(goroot):
+
+        goroot = None
+      gab = os.path.join(sdk_base, GO_APP_BUILDER)
+      if os.path.exists(gab):
         app_paths = list(paths)
         go_files = [f for f in app_paths
                     if f.endswith('.go') and not appyaml.nobuild_files.match(f)]
@@ -3315,13 +3417,15 @@
           raise RuntimeError('no Go source files to upload '
                              '(-nobuild_files applied)')
         gab_argv = [
-            os.path.join(goroot, 'bin', 'go-app-builder'),
+            gab,
             '-app_base', self.basepath,
             '-arch', '6',
             '-gopath', gopath,
-            '-goroot', goroot,
             '-print_extras',
-        ] + go_files
+        ]
+        if goroot:
+          gab_argv.extend(['-goroot', goroot])
+        gab_argv.extend(go_files)
 
         env = {
             'GOOS': 'linux',
@@ -3392,7 +3496,6 @@
     if (self._JavaSupported() and
         appcfg_java.IsWarFileWithoutYaml(self.basepath)):
       java_app_update = appcfg_java.JavaAppUpdate(self.basepath, self.options)
-      sdk_root = os.path.dirname(appcfg_java.__file__)
       self.options.compile_jsps = True
 
 
@@ -3401,6 +3504,7 @@
 
 
 
+      sdk_root = os.path.dirname(appcfg_java.__file__)
       self.stage_dir = java_app_update.CreateStagingDirectory(sdk_root)
       try:
         appyaml = self._ParseAppInfoFromYaml(
@@ -3410,7 +3514,8 @@
       finally:
         if self.options.retain_upload_dir:
           StatusUpdate(
-              'Temporary staging directory left in %s' % self.stage_dir)
+              'Temporary staging directory left in %s' % self.stage_dir,
+              self.error_fh)
         else:
           shutil.rmtree(self.stage_dir)
     else:
@@ -3442,7 +3547,7 @@
 
     def _AbortAppMismatch(yaml_name):
       StatusUpdate('Error: Aborting upload because application in %s does not '
-                   'match application in app.yaml' % yaml_name)
+                   'match application in app.yaml' % yaml_name, self.error_fh)
 
 
     dos_yaml = self._ParseDosYaml(basepath, appyaml)
@@ -3464,6 +3569,12 @@
     if index_defs and index_defs.application != appyaml.application:
       _AbortAppMismatch('index.yaml')
       return
+
+    dispatch_yaml = self._ParseDispatchYaml(basepath, appyaml)
+    if dispatch_yaml and dispatch_yaml.application != appyaml.application:
+      _AbortAppMismatch('dispatch.yaml')
+      return
+
     self.UpdateVersion(rpcserver, basepath, appyaml, APP_YAML_FILENAME)
 
     if appyaml.runtime == 'python':
@@ -3479,7 +3590,7 @@
 
 
     if index_defs:
-      index_upload = IndexDefinitionUpload(rpcserver, index_defs)
+      index_upload = IndexDefinitionUpload(rpcserver, index_defs, self.error_fh)
       try:
         index_upload.DoUpload()
       except urllib2.HTTPError, e:
@@ -3492,23 +3603,30 @@
 
 
     if cron_yaml:
-      cron_upload = CronEntryUpload(rpcserver, cron_yaml)
+      cron_upload = CronEntryUpload(rpcserver, cron_yaml, self.error_fh)
       cron_upload.DoUpload()
 
 
     if queue_yaml:
-      queue_upload = QueueEntryUpload(rpcserver, queue_yaml)
+      queue_upload = QueueEntryUpload(rpcserver, queue_yaml, self.error_fh)
       queue_upload.DoUpload()
 
 
     if dos_yaml:
-      dos_upload = DosEntryUpload(rpcserver, dos_yaml)
+      dos_upload = DosEntryUpload(rpcserver, dos_yaml, self.error_fh)
       dos_upload.DoUpload()
 
 
+    if dispatch_yaml:
+      dispatch_upload = DispatchEntryUpload(rpcserver,
+                                            dispatch_yaml,
+                                            self.error_fh)
+      dispatch_upload.DoUpload()
+
+
     if appyaml:
       pagespeed_upload = PagespeedEntryUpload(
-          rpcserver, appyaml, appyaml.pagespeed)
+          rpcserver, appyaml, appyaml.pagespeed, self.error_fh)
       try:
         pagespeed_upload.DoUpload()
       except urllib2.HTTPError, e:
@@ -3570,10 +3688,11 @@
 
     cron_yaml = self._ParseCronYaml(self.basepath)
     if cron_yaml:
-      cron_upload = CronEntryUpload(rpcserver, cron_yaml)
+      cron_upload = CronEntryUpload(rpcserver, cron_yaml, self.error_fh)
       cron_upload.DoUpload()
     else:
-      print >>sys.stderr, 'Could not find cron configuration. No action taken.'
+      print >>self.error_fh, (
+          'Could not find cron configuration. No action taken.')
 
   def UpdateIndexes(self):
     """Updates indexes."""
@@ -3585,10 +3704,11 @@
 
     index_defs = self._ParseIndexYaml(self.basepath)
     if index_defs:
-      index_upload = IndexDefinitionUpload(rpcserver, index_defs)
+      index_upload = IndexDefinitionUpload(rpcserver, index_defs, self.error_fh)
       index_upload.DoUpload()
     else:
-      print >>sys.stderr, 'Could not find index configuration. No action taken.'
+      print >>self.error_fh, (
+          'Could not find index configuration. No action taken.')
 
   def UpdateQueues(self):
     """Updates any new or changed task queue definitions."""
@@ -3599,10 +3719,11 @@
 
     queue_yaml = self._ParseQueueYaml(self.basepath)
     if queue_yaml:
-      queue_upload = QueueEntryUpload(rpcserver, queue_yaml)
+      queue_upload = QueueEntryUpload(rpcserver, queue_yaml, self.error_fh)
       queue_upload.DoUpload()
     else:
-      print >>sys.stderr, 'Could not find queue configuration. No action taken.'
+      print >>self.error_fh, (
+          'Could not find queue configuration. No action taken.')
 
   def UpdateDispatch(self):
     """Updates new or changed dispatch definitions."""
@@ -3614,18 +3735,13 @@
 
     dispatch_yaml = self._ParseDispatchYaml(self.basepath)
     if dispatch_yaml:
-      if self.options.app_id:
-        dispatch_yaml.application = self.options.app_id
-      if not dispatch_yaml.application:
-        self.parser.error('Expected -A app_id when dispatch.yaml.application'
-                          ' is not set.')
-      StatusUpdate('Uploading dispatch entries.')
-      rpcserver.Send('/api/dispatch/update',
-                     app_id=dispatch_yaml.application,
-                     payload=dispatch_yaml.ToYAML())
+      dispatch_upload = DispatchEntryUpload(rpcserver,
+                                            dispatch_yaml,
+                                            self.error_fh)
+      dispatch_upload.DoUpload()
     else:
-      print >>sys.stderr, ('Could not find dispatch configuration. No action'
-                           ' taken.')
+      print >>self.error_fh, ('Could not find dispatch configuration. No action'
+                              ' taken.')
 
   def UpdateDos(self):
     """Updates any new or changed dos definitions."""
@@ -3636,10 +3752,11 @@
 
     dos_yaml = self._ParseDosYaml(self.basepath)
     if dos_yaml:
-      dos_upload = DosEntryUpload(rpcserver, dos_yaml)
+      dos_upload = DosEntryUpload(rpcserver, dos_yaml, self.error_fh)
       dos_upload.DoUpload()
     else:
-      print >>sys.stderr, 'Could not find dos configuration. No action taken.'
+      print >>self.error_fh, (
+          'Could not find dos configuration. No action taken.')
 
   def BackendsAction(self):
     """Placeholder; we never expect this action to be invoked."""
@@ -3661,14 +3778,14 @@
           'Error: Backends are not supported with the PHP runtime. '
           'Please use Modules instead.\n')
 
-  def BackendsYamlCheck(self, appyaml, backend=None):
+  def BackendsYamlCheck(self, basepath, appyaml, backend=None):
     """Check the backends.yaml file is sane and which backends to update."""
 
 
     if appyaml.backends:
       self.parser.error('Backends are not allowed in app.yaml.')
 
-    backends_yaml = self._ParseBackendsYaml(self.basepath)
+    backends_yaml = self._ParseBackendsYaml(basepath)
     appyaml.backends = backends_yaml.backends
 
     if not appyaml.backends:
@@ -3704,17 +3821,25 @@
       self.backend = self.args[0]
     elif len(self.args) > 1:
       self.parser.error('Expected an optional <backend> argument.')
+    if (self._JavaSupported() and
+        appcfg_java.IsWarFileWithoutYaml(self.basepath)):
+      java_app_update = appcfg_java.JavaAppUpdate(self.basepath, self.options)
+      self.options.compile_jsps = True
+      sdk_root = os.path.dirname(appcfg_java.__file__)
+      basepath = java_app_update.CreateStagingDirectory(sdk_root)
+    else:
+      basepath = self.basepath
 
     yaml_file_basename = 'app'
-    appyaml = self._ParseAppInfoFromYaml(self.basepath,
+    appyaml = self._ParseAppInfoFromYaml(basepath,
                                          basename=yaml_file_basename)
-    BackendsStatusUpdate(appyaml.runtime)
+    BackendsStatusUpdate(appyaml.runtime, self.error_fh)
     self.BackendsPhpCheck(appyaml)
     rpcserver = self._GetRpcServer()
 
-    backends_to_update = self.BackendsYamlCheck(appyaml, self.backend)
+    backends_to_update = self.BackendsYamlCheck(basepath, appyaml, self.backend)
     for backend in backends_to_update:
-      self.UpdateVersion(rpcserver, self.basepath, appyaml, yaml_file_basename,
+      self.UpdateVersion(rpcserver, basepath, appyaml, yaml_file_basename,
                          backend=backend)
 
   def BackendsList(self):
@@ -3726,7 +3851,7 @@
 
 
     appyaml = self._ParseAppInfoFromYaml(self.basepath)
-    BackendsStatusUpdate(appyaml.runtime)
+    BackendsStatusUpdate(appyaml.runtime, self.error_fh)
     rpcserver = self._GetRpcServer()
     response = rpcserver.Send('/api/backends/list', app_id=appyaml.application)
     print >> self.out_fh, response
@@ -3745,7 +3870,7 @@
 
     backend = self.args[0]
     appyaml = self._ParseAppInfoFromYaml(self.basepath)
-    BackendsStatusUpdate(appyaml.runtime)
+    BackendsStatusUpdate(appyaml.runtime, self.error_fh)
     self.BackendsPhpCheck(appyaml)
     rpcserver = self._GetRpcServer()
     response = rpcserver.Send('/api/backends/start',
@@ -3760,7 +3885,7 @@
 
     backend = self.args[0]
     appyaml = self._ParseAppInfoFromYaml(self.basepath)
-    BackendsStatusUpdate(appyaml.runtime)
+    BackendsStatusUpdate(appyaml.runtime, self.error_fh)
     rpcserver = self._GetRpcServer()
     response = rpcserver.Send('/api/backends/stop',
                               app_id=appyaml.application,
@@ -3774,7 +3899,7 @@
 
     backend = self.args[0]
     appyaml = self._ParseAppInfoFromYaml(self.basepath)
-    BackendsStatusUpdate(appyaml.runtime)
+    BackendsStatusUpdate(appyaml.runtime, self.error_fh)
     rpcserver = self._GetRpcServer()
     response = rpcserver.Send('/api/backends/delete',
                               app_id=appyaml.application,
@@ -3788,7 +3913,7 @@
 
     backend = self.args[0]
     appyaml = self._ParseAppInfoFromYaml(self.basepath)
-    BackendsStatusUpdate(appyaml.runtime)
+    BackendsStatusUpdate(appyaml.runtime, self.error_fh)
     self.BackendsPhpCheck(appyaml)
     backends_yaml = self._ParseBackendsYaml(self.basepath)
     rpcserver = self._GetRpcServer()
@@ -3800,17 +3925,24 @@
 
   def ListVersions(self):
     """Lists all versions for an app."""
-    if self.args:
-      self.parser.error('Expected no arguments.')
+    if len(self.args) == 0:
+      if not self.options.app_id:
+        self.parser.error('Expected <directory> argument or -A <app id>.')
+      app_id = self.options.app_id
+    elif len(self.args) == 1:
+      if self.options.app_id:
+        self.parser.error('<directory> argument is not needed with -A.')
+      appyaml = self._ParseAppInfoFromYaml(self.args[0])
+      app_id = appyaml.application
+    else:
+      self.parser.error('Expected 1 argument, not %d.' % len(self.args))
 
-    appyaml = self._ParseAppInfoFromYaml(self.basepath)
     rpcserver = self._GetRpcServer()
-    response = rpcserver.Send('/api/versions/list', app_id=appyaml.application)
+    response = rpcserver.Send('/api/versions/list', app_id=app_id)
 
     parsed_response = yaml.safe_load(response)
     if not parsed_response:
-      print >> self.out_fh, ('No versions uploaded for app: %s.' %
-                             appyaml.application)
+      print >> self.out_fh, ('No versions uploaded for app: %s.' % app_id)
     else:
       print >> self.out_fh, response
 
@@ -3848,10 +3980,6 @@
     else:
       self._PrintHelpAndExit()
 
-    if self.options.instance is None:
-      self.parser.error(
-          ('Expected an --instance flag.'))
-
 
 
     if self.options.app_id:
@@ -3865,19 +3993,9 @@
     response = rpcserver.Send('/api/vms/debug',
                               app_id=app_id,
                               version_match=version,
-                              module=module,
-                              instance=self.options.instance)
+                              module=module)
     print >> self.out_fh, response
 
-  def _DebugActionOptions(self, parser):
-    """Adds debug-specific options to 'parser'.
-
-    Args:
-      parser: An instance of OptionsParser.
-    """
-    parser.add_option('-I', '--instance', type='int', dest='instance',
-                      help='Instance to debug.')
-
   def _ParseAndValidateModuleYamls(self, yaml_paths):
     """Validates given yaml paths and returns the parsed yaml objects.
 
@@ -4054,7 +4172,8 @@
     version_setter = DefaultVersionSet(self._GetRpcServer(),
                                        app_id,
                                        module,
-                                       version)
+                                       version,
+                                       self.error_fh)
     version_setter.SetVersion()
 
 
@@ -4079,7 +4198,7 @@
       version = self.options.version
 
     traffic_migrator = TrafficMigrator(
-        self._GetRpcServer(), app_id, version)
+        self._GetRpcServer(), app_id, version, self.error_fh)
     traffic_migrator.MigrateTraffic()
 
   def RequestLogs(self):
@@ -4383,7 +4502,7 @@
       run_fn = self.RunBulkloader
     self._SetupLoad()
 
-    StatusUpdate('Downloading data records.')
+    StatusUpdate('Downloading data records.', self.error_fh)
 
     args = self._MakeLoaderArgs()
     args['download'] = bool(args['config_file'])
@@ -4405,7 +4524,7 @@
       run_fn = self.RunBulkloader
     self._SetupLoad()
 
-    StatusUpdate('Uploading data records.')
+    StatusUpdate('Uploading data records.', self.error_fh)
 
     args = self._MakeLoaderArgs()
     args['download'] = False
@@ -4426,7 +4545,7 @@
       run_fn = self.RunBulkloader
     self._SetupLoad()
 
-    StatusUpdate('Creating bulkloader configuration.')
+    StatusUpdate('Creating bulkloader configuration.', self.error_fh)
 
     args = self._MakeLoaderArgs()
     args['download'] = False
@@ -4547,7 +4666,8 @@
       output: The file handle to write the output to (used for testing).
     """
     appyaml = self._ParseAppInfoFromYaml(self.basepath)
-    resource_limits = GetResourceLimits(self._GetRpcServer(), appyaml)
+    resource_limits = GetResourceLimits(
+        self._GetRpcServer(), appyaml, self.error_fh)
 
 
     for attr_name in sorted(resource_limits):
@@ -4888,11 +5008,12 @@
 
       'list_versions': Action(
           function='ListVersions',
-          usage='%prog [options] list_versions <directory>',
+          usage='%prog [options] list_versions [directory]',
           short_desc='List all uploaded versions for an app.',
           long_desc="""
 The 'list_versions' command outputs the uploaded versions for each module of
-an application in YAML."""),
+an application in YAML.""",
+          uses_basepath=False),
 
       'delete_version': Action(
           function='DeleteVersion',
@@ -4906,15 +5027,13 @@
 
       'debug': Action(
           function='DebugAction',
-          usage='%prog [options] debug -I instance [-A app_id] [-V version] '
+          usage='%prog [options] debug [-A app_id] [-V version] '
           ' [-M module] [directory]',
-          options=_DebugActionOptions,
           short_desc='Debug a vm runtime application.',
           hidden=True,
           uses_basepath=False,
           long_desc="""
-The 'debug' command configures a vm runtime instance to be accessable for
-debugging."""),
+The 'debug' command configures a vm runtime to be accessable for debugging."""),
   }
 
 
diff --git a/google/appengine/tools/appcfg_java.py b/google/appengine/tools/appcfg_java.py
index d9c9af5..8b86fea 100644
--- a/google/appengine/tools/appcfg_java.py
+++ b/google/appengine/tools/appcfg_java.py
@@ -60,11 +60,8 @@
   if os.path.isfile(os.path.join(dir_path, 'app.yaml')):
     return False
   web_inf = os.path.join(dir_path, 'WEB-INF')
-  if not os.path.isdir(web_inf):
-    return False
-  if not set(['appengine-web.xml', 'web.xml']).issubset(os.listdir(web_inf)):
-    return False
-  return True
+  return (os.path.isdir(web_inf) and
+          set(['appengine-web.xml', 'web.xml']).issubset(os.listdir(web_inf)))
 
 
 def AddUpdateOptions(parser):
@@ -107,6 +104,8 @@
       self.xml_to_yaml_function = xml_to_yaml_function
 
   _XML_PARSERS = [
+      _XmlParser('backends.xml', 'backends.yaml',
+                 backends_xml_parser.GetBackendsYaml),
       _XmlParser('cron.xml', 'cron.yaml', cron_xml_parser.GetCronYaml),
       _XmlParser('datastore-indexes.xml', 'index.yaml',
                  indexes_xml_parser.GetIndexYaml),
@@ -188,12 +187,6 @@
         file_name='web.xml',
         parser=web_xml_parser.WebXmlParser)
 
-  def _ReadBackendsXml(self):
-    return self._ReadAndParseXml(
-        basepath=self.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())
@@ -266,12 +259,10 @@
 
     return stage_dir
 
-  def GenerateAppYamlString(self, basepath, static_file_list, api_version=None):
+  def GenerateAppYamlString(self, static_file_list, api_version=None):
     """Constructs an app.yaml string equivalent to the XML files under WEB-INF.
 
     Args:
-      basepath: a string that is the path to the WEB-INF directory. This
-        might not be self.basepath, because it might be a staging directory.
       static_file_list: a list of strings that are the absolute path names of
         static file resources.
       api_version: a string that is the Java API version number, or None if
@@ -281,12 +272,8 @@
       A string that would have the same effect as the XML files under WEB-INF
       if it were the contents of an app.yaml file.
     """
-    backends = []
-    if os.path.isfile(os.path.join(self.basepath, 'WEB-INF', 'backends.xml')):
-      backends = self._ReadBackendsXml(basepath)
     return yaml_translator.AppYamlTranslator(
         self.app_engine_web_xml,
-        backends,
         self.web_xml,
         static_file_list,
         api_version).GetYaml()
@@ -298,8 +285,7 @@
       The path to the WEB-INF/appengine-generated directory.
     """
     static_file_list = self._GetStaticFileList(stage_dir)
-    yaml_str = self.GenerateAppYamlString(
-        stage_dir, static_file_list, api_version)
+    yaml_str = self.GenerateAppYamlString(static_file_list, api_version)
     if not os.path.isdir(appengine_generated):
       os.mkdir(appengine_generated)
     with open(os.path.join(appengine_generated, 'app.yaml'), 'w') as handle:
diff --git a/google/appengine/tools/backends_xml_parser.py b/google/appengine/tools/backends_xml_parser.py
index c6f0296..8a0344e 100644
--- a/google/appengine/tools/backends_xml_parser.py
+++ b/google/appengine/tools/backends_xml_parser.py
@@ -16,7 +16,7 @@
 #
 """Directly processes text of backends.xml.
 
-BackendsXmlParser is called with an XML string to produce a BackendsXml object
+BackendsXmlParser is called with an XML string to produce a Backends object
 containing the data from the XML.
 
 BackendsXmlParser: converts XML to BackendsXml objct
@@ -31,6 +31,15 @@
 from google.appengine.tools.value_mixin import ValueMixin
 
 
+def GetBackendsYaml(unused_application, backends_xml_str):
+  """Translates a backends.xml string into a backends.yaml string."""
+  backend_list = BackendsXmlParser().ProcessXml(backends_xml_str)
+  statements = ['backends:']
+  for backend in backend_list:
+    statements += backend.ToYaml()
+  return '\n'.join(statements) + '\n'
+
+
 class BackendsXmlParser(object):
   """Provides logic for walking down XML tree and pulling data."""
 
@@ -60,7 +69,7 @@
       raise AppEngineConfigException('Bad input -- not valid XML')
 
   def ProcessBackendNode(self, node):
-    """Processes XML nodes labeled 'backend' into a BackendsXml object."""
+    """Processes XML nodes labeled 'backend' into a Backends object."""
     tag = xml_parser_utils.GetTag(node)
     if tag != 'backend':
       self.errors.append('Unrecognized node: <%s>' % tag)
@@ -124,3 +133,19 @@
     self.options = set()
 
 
+
+  def ToYaml(self):
+    """Convert the backend specification into a list of YAML lines."""
+    statements = ['- name: %s' % self.name]
+    for entry, field in [
+        ('instances', self.instances),
+        ('class', self.instance_class),
+        ('max_concurrent_requests', self.max_concurrent_requests)]:
+      if field is not None:
+        statements += ['  %s: %s' % (entry, str(field))]
+
+    if self.options:
+      options_str = ', '.join(sorted(list(self.options)))
+      statements += ['  options: %s' % options_str]
+
+    return statements
diff --git a/google/appengine/tools/dev-channel-js.js b/google/appengine/tools/dev-channel-js.js
index b6f1c03..f71bd72 100644
--- a/google/appengine/tools/dev-channel-js.js
+++ b/google/appengine/tools/dev-channel-js.js
@@ -22,6 +22,8 @@
     throw opt_message = opt_message || "", Error("Importing test-only code into non-debug environment" + opt_message ? ": " + opt_message : ".");
   }
 };
+goog.forwardDeclare = function() {
+};
 goog.getObjectByName = function(name, opt_obj) {
   for (var parts = name.split("."), cur = opt_obj || goog.global, part;part = parts.shift();) {
     if (goog.isDefAndNotNull(cur[part])) {
@@ -384,7 +386,12 @@
 });
 goog.debug = {};
 goog.debug.Error = function(opt_msg) {
-  Error.captureStackTrace ? Error.captureStackTrace(this, goog.debug.Error) : this.stack = Error().stack || "";
+  if (Error.captureStackTrace) {
+    Error.captureStackTrace(this, goog.debug.Error);
+  } else {
+    var stack = Error().stack;
+    stack && (this.stack = stack);
+  }
   opt_msg && (this.message = String(opt_msg));
 };
 goog.inherits(goog.debug.Error, Error);
@@ -819,11 +826,12 @@
 };
 goog.array = {};
 goog.NATIVE_ARRAY_PROTOTYPES = goog.TRUSTED_SITE;
+goog.array.ASSUME_NATIVE_FUNCTIONS = !1;
 goog.array.peek = function(array) {
   return array[array.length - 1];
 };
 goog.array.ARRAY_PROTOTYPE_ = Array.prototype;
-goog.array.indexOf = goog.NATIVE_ARRAY_PROTOTYPES && goog.array.ARRAY_PROTOTYPE_.indexOf ? function(arr, obj, opt_fromIndex) {
+goog.array.indexOf = goog.NATIVE_ARRAY_PROTOTYPES && (goog.array.ASSUME_NATIVE_FUNCTIONS || goog.array.ARRAY_PROTOTYPE_.indexOf) ? function(arr, obj, opt_fromIndex) {
   goog.asserts.assert(null != arr.length);
   return goog.array.ARRAY_PROTOTYPE_.indexOf.call(arr, obj, opt_fromIndex);
 } : function(arr, obj, opt_fromIndex) {
@@ -838,7 +846,7 @@
   }
   return-1;
 };
-goog.array.lastIndexOf = goog.NATIVE_ARRAY_PROTOTYPES && goog.array.ARRAY_PROTOTYPE_.lastIndexOf ? function(arr, obj, opt_fromIndex) {
+goog.array.lastIndexOf = goog.NATIVE_ARRAY_PROTOTYPES && (goog.array.ASSUME_NATIVE_FUNCTIONS || goog.array.ARRAY_PROTOTYPE_.lastIndexOf) ? function(arr, obj, opt_fromIndex) {
   goog.asserts.assert(null != arr.length);
   var fromIndex = null == opt_fromIndex ? arr.length - 1 : opt_fromIndex;
   return goog.array.ARRAY_PROTOTYPE_.lastIndexOf.call(arr, obj, fromIndex);
@@ -855,7 +863,7 @@
   }
   return-1;
 };
-goog.array.forEach = goog.NATIVE_ARRAY_PROTOTYPES && goog.array.ARRAY_PROTOTYPE_.forEach ? function(arr, f, opt_obj) {
+goog.array.forEach = goog.NATIVE_ARRAY_PROTOTYPES && (goog.array.ASSUME_NATIVE_FUNCTIONS || goog.array.ARRAY_PROTOTYPE_.forEach) ? function(arr, f, opt_obj) {
   goog.asserts.assert(null != arr.length);
   goog.array.ARRAY_PROTOTYPE_.forEach.call(arr, f, opt_obj);
 } : function(arr, f, opt_obj) {
@@ -868,7 +876,7 @@
     i in arr2 && f.call(opt_obj, arr2[i], i, arr);
   }
 };
-goog.array.filter = goog.NATIVE_ARRAY_PROTOTYPES && goog.array.ARRAY_PROTOTYPE_.filter ? function(arr, f, opt_obj) {
+goog.array.filter = goog.NATIVE_ARRAY_PROTOTYPES && (goog.array.ASSUME_NATIVE_FUNCTIONS || goog.array.ARRAY_PROTOTYPE_.filter) ? function(arr, f, opt_obj) {
   goog.asserts.assert(null != arr.length);
   return goog.array.ARRAY_PROTOTYPE_.filter.call(arr, f, opt_obj);
 } : function(arr, f, opt_obj) {
@@ -880,7 +888,7 @@
   }
   return res;
 };
-goog.array.map = goog.NATIVE_ARRAY_PROTOTYPES && goog.array.ARRAY_PROTOTYPE_.map ? function(arr, f, opt_obj) {
+goog.array.map = goog.NATIVE_ARRAY_PROTOTYPES && (goog.array.ASSUME_NATIVE_FUNCTIONS || goog.array.ARRAY_PROTOTYPE_.map) ? function(arr, f, opt_obj) {
   goog.asserts.assert(null != arr.length);
   return goog.array.ARRAY_PROTOTYPE_.map.call(arr, f, opt_obj);
 } : function(arr, f, opt_obj) {
@@ -889,7 +897,7 @@
   }
   return res;
 };
-goog.array.reduce = goog.NATIVE_ARRAY_PROTOTYPES && goog.array.ARRAY_PROTOTYPE_.reduce ? function(arr, f, val, opt_obj) {
+goog.array.reduce = goog.NATIVE_ARRAY_PROTOTYPES && (goog.array.ASSUME_NATIVE_FUNCTIONS || goog.array.ARRAY_PROTOTYPE_.reduce) ? function(arr, f, val, opt_obj) {
   goog.asserts.assert(null != arr.length);
   opt_obj && (f = goog.bind(f, opt_obj));
   return goog.array.ARRAY_PROTOTYPE_.reduce.call(arr, f, val);
@@ -900,7 +908,7 @@
   });
   return rval;
 };
-goog.array.reduceRight = goog.NATIVE_ARRAY_PROTOTYPES && goog.array.ARRAY_PROTOTYPE_.reduceRight ? function(arr, f, val, opt_obj) {
+goog.array.reduceRight = goog.NATIVE_ARRAY_PROTOTYPES && (goog.array.ASSUME_NATIVE_FUNCTIONS || goog.array.ARRAY_PROTOTYPE_.reduceRight) ? function(arr, f, val, opt_obj) {
   goog.asserts.assert(null != arr.length);
   opt_obj && (f = goog.bind(f, opt_obj));
   return goog.array.ARRAY_PROTOTYPE_.reduceRight.call(arr, f, val);
@@ -911,7 +919,7 @@
   });
   return rval;
 };
-goog.array.some = goog.NATIVE_ARRAY_PROTOTYPES && goog.array.ARRAY_PROTOTYPE_.some ? function(arr, f, opt_obj) {
+goog.array.some = goog.NATIVE_ARRAY_PROTOTYPES && (goog.array.ASSUME_NATIVE_FUNCTIONS || goog.array.ARRAY_PROTOTYPE_.some) ? function(arr, f, opt_obj) {
   goog.asserts.assert(null != arr.length);
   return goog.array.ARRAY_PROTOTYPE_.some.call(arr, f, opt_obj);
 } : function(arr, f, opt_obj) {
@@ -922,7 +930,7 @@
   }
   return!1;
 };
-goog.array.every = goog.NATIVE_ARRAY_PROTOTYPES && goog.array.ARRAY_PROTOTYPE_.every ? function(arr, f, opt_obj) {
+goog.array.every = goog.NATIVE_ARRAY_PROTOTYPES && (goog.array.ASSUME_NATIVE_FUNCTIONS || goog.array.ARRAY_PROTOTYPE_.every) ? function(arr, f, opt_obj) {
   goog.asserts.assert(null != arr.length);
   return goog.array.ARRAY_PROTOTYPE_.every.call(arr, f, opt_obj);
 } : function(arr, f, opt_obj) {
@@ -1066,8 +1074,7 @@
   return found ? left : ~left;
 };
 goog.array.sort = function(arr, opt_compareFn) {
-  goog.asserts.assert(null != arr.length);
-  goog.array.ARRAY_PROTOTYPE_.sort.call(arr, opt_compareFn || goog.array.defaultCompare);
+  arr.sort(opt_compareFn || goog.array.defaultCompare);
 };
 goog.array.stableSort = function(arr, opt_compareFn) {
   function stableCompareFn(obj1, obj2) {
@@ -1108,9 +1115,6 @@
   }
   return!0;
 };
-goog.array.compare = function(arr1, arr2, opt_equalsFn) {
-  return goog.array.equals(arr1, arr2, opt_equalsFn);
-};
 goog.array.compare3 = function(arr1, arr2, opt_compareFn) {
   for (var compare = opt_compareFn || goog.array.defaultCompare, l = Math.min(arr1.length, arr2.length), i = 0;i < l;i++) {
     var result = compare(arr1[i], arr2[i]);
@@ -1410,6 +1414,13 @@
 goog.math.isFiniteNumber = function(num) {
   return isFinite(num) && !isNaN(num);
 };
+goog.math.log10Floor = function(num) {
+  if (0 < num) {
+    var x = Math.round(Math.log(num) * Math.LOG10E);
+    return x - (Math.pow(10, x) > num);
+  }
+  return 0 == num ? -Infinity : NaN;
+};
 goog.math.safeFloor = function(num, opt_epsilon) {
   goog.asserts.assert(!goog.isDef(opt_epsilon) || 0 < opt_epsilon);
   return Math.floor(num + (opt_epsilon || 2E-15));
@@ -2786,7 +2797,7 @@
 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"), POINTERDOWN:"pointerdown", POINTERUP:"pointerup", POINTERCANCEL:"pointercancel", POINTERMOVE:"pointermove", POINTEROVER:"pointerover", POINTEROUT:"pointerout", POINTERENTER:"pointerenter", POINTERLEAVE:"pointerleave", GOTPOINTERCAPTURE:"gotpointercapture", LOSTPOINTERCAPTURE:"lostpointercapture", MSGESTURECHANGE:"MSGestureChange", MSGESTUREEND:"MSGestureEnd", MSGESTUREHOLD:"MSGestureHold", MSGESTURESTART:"MSGestureStart", MSGESTURETAP:"MSGestureTap", 
 MSGOTPOINTERCAPTURE:"MSGotPointerCapture", MSINERTIASTART:"MSInertiaStart", MSLOSTPOINTERCAPTURE:"MSLostPointerCapture", MSPOINTERCANCEL:"MSPointerCancel", MSPOINTERDOWN:"MSPointerDown", MSPOINTERENTER:"MSPointerEnter", MSPOINTERHOVER:"MSPointerHover", MSPOINTERLEAVE:"MSPointerLeave", MSPOINTERMOVE:"MSPointerMove", MSPOINTEROUT:"MSPointerOut", MSPOINTEROVER:"MSPointerOver", 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"};
+COMPOSITIONEND:"compositionend", EXIT:"exit", LOADABORT:"loadabort", LOADCOMMIT:"loadcommit", LOADREDIRECT:"loadredirect", LOADSTART:"loadstart", LOADSTOP:"loadstop", RESPONSIVE:"responsive", SIZECHANGED:"sizechanged", UNRESPONSIVE:"unresponsive", VISIBILITYCHANGE:"visibilitychange", STORAGE:"storage"};
 goog.events.BrowserEvent = function(opt_e, opt_currentTarget) {
   goog.events.Event.call(this, opt_e ? opt_e.type : "");
   this.relatedTarget = this.currentTarget = this.target = null;
@@ -2887,22 +2898,23 @@
   return this.typeCount_;
 };
 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_++);
+  var typeStr = type.toString(), listenerArray = this.listeners[typeStr];
+  listenerArray || (listenerArray = this.listeners[typeStr] = [], this.typeCount_++);
   var listenerObj, index = goog.events.ListenerMap.findListenerIndex_(listenerArray, listener, opt_useCapture, opt_listenerScope);
-  -1 < index ? (listenerObj = listenerArray[index], callOnce || (listenerObj.callOnce = !1)) : (listenerObj = new goog.events.Listener(listener, null, this.src, type, !!opt_useCapture, opt_listenerScope), listenerObj.callOnce = callOnce, listenerArray.push(listenerObj));
+  -1 < index ? (listenerObj = listenerArray[index], callOnce || (listenerObj.callOnce = !1)) : (listenerObj = new goog.events.Listener(listener, null, this.src, typeStr, !!opt_useCapture, opt_listenerScope), listenerObj.callOnce = callOnce, listenerArray.push(listenerObj));
   return listenerObj;
 };
 goog.events.ListenerMap.prototype.remove = function(type, listener, opt_useCapture, opt_listenerScope) {
-  if (!(type in this.listeners)) {
+  var typeStr = type.toString();
+  if (!(typeStr in this.listeners)) {
     return!1;
   }
-  var listenerArray = this.listeners[type], index = goog.events.ListenerMap.findListenerIndex_(listenerArray, listener, opt_useCapture, opt_listenerScope);
+  var listenerArray = this.listeners[typeStr], index = goog.events.ListenerMap.findListenerIndex_(listenerArray, listener, opt_useCapture, opt_listenerScope);
   if (-1 < index) {
     var listenerObj = listenerArray[index];
     listenerObj.markAsRemoved();
     goog.array.removeAt(listenerArray, index);
-    0 == listenerArray.length && (delete this.listeners[type], this.typeCount_--);
+    0 == listenerArray.length && (delete this.listeners[typeStr], this.typeCount_--);
     return!0;
   }
   return!1;
@@ -2917,9 +2929,9 @@
   return removed;
 };
 goog.events.ListenerMap.prototype.removeAll = function(opt_type) {
-  var count = 0, type;
+  var typeStr = opt_type && opt_type.toString(), count = 0, type;
   for (type in this.listeners) {
-    if (!opt_type || type == opt_type) {
+    if (!typeStr || type == typeStr) {
       for (var listenerArray = this.listeners[type], i = 0;i < listenerArray.length;i++) {
         ++count, listenerArray[i].markAsRemoved();
       }
@@ -2930,7 +2942,7 @@
   return count;
 };
 goog.events.ListenerMap.prototype.getListeners = function(type, capture) {
-  var listenerArray = this.listeners[type], rv = [];
+  var listenerArray = this.listeners[type.toString()], rv = [];
   if (listenerArray) {
     for (var i = 0;i < listenerArray.length;++i) {
       var listenerObj = listenerArray[i];
@@ -2940,15 +2952,15 @@
   return rv;
 };
 goog.events.ListenerMap.prototype.getListener = function(type, listener, capture, opt_listenerScope) {
-  var listenerArray = this.listeners[type], i = -1;
+  var listenerArray = this.listeners[type.toString()], i = -1;
   listenerArray && (i = goog.events.ListenerMap.findListenerIndex_(listenerArray, listener, capture, opt_listenerScope));
   return-1 < i ? listenerArray[i] : null;
 };
 goog.events.ListenerMap.prototype.hasListener = function(opt_type, opt_capture) {
-  var hasType = goog.isDef(opt_type), hasCapture = goog.isDef(opt_capture);
+  var hasType = goog.isDef(opt_type), typeStr = hasType ? opt_type.toString() : "", hasCapture = goog.isDef(opt_capture);
   return goog.object.some(this.listeners, function(listenerArray) {
     for (var i = 0;i < listenerArray.length;++i) {
-      if (!(hasType && listenerArray[i].type != opt_type || hasCapture && listenerArray[i].capture != opt_capture)) {
+      if (!(hasType && listenerArray[i].type != typeStr || hasCapture && listenerArray[i].capture != opt_capture)) {
         return!0;
       }
     }
@@ -2978,7 +2990,7 @@
     }
     return null;
   }
-  listener = goog.events.wrapListener_(listener);
+  listener = goog.events.wrapListener(listener);
   return goog.events.Listenable.isImplementedBy(src) ? src.listen(type, listener, opt_capt, opt_handler) : goog.events.listen_(src, type, listener, !1, opt_capt, opt_handler);
 };
 goog.events.listen_ = function(src, type, listener, callOnce, opt_capt, opt_handler) {
@@ -3026,7 +3038,7 @@
     }
     return null;
   }
-  listener = goog.events.wrapListener_(listener);
+  listener = goog.events.wrapListener(listener);
   return goog.events.Listenable.isImplementedBy(src) ? src.listenOnce(type, listener, opt_capt, opt_handler) : goog.events.listen_(src, type, listener, !0, opt_capt, opt_handler);
 };
 goog.events.listenWithWrapper = function(src, wrapper, listener, opt_capt, opt_handler) {
@@ -3039,7 +3051,7 @@
     }
     return null;
   }
-  listener = goog.events.wrapListener_(listener);
+  listener = goog.events.wrapListener(listener);
   if (goog.events.Listenable.isImplementedBy(src)) {
     return src.unlisten(type, listener, opt_capt, opt_handler);
   }
@@ -3112,7 +3124,7 @@
   return listenerMap ? listenerMap.getListeners(type, capture) : [];
 };
 goog.events.getListener = function(src, type, listener, opt_capt, opt_handler) {
-  listener = goog.events.wrapListener_(listener);
+  listener = goog.events.wrapListener(listener);
   var capture = !!opt_capt;
   if (goog.events.Listenable.isImplementedBy(src)) {
     return src.getListener(type, listener, capture, opt_handler);
@@ -3223,7 +3235,7 @@
   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) {
+goog.events.wrapListener = function(listener) {
   goog.asserts.assert(listener, "Listener can not be null.");
   if (goog.isFunction(listener)) {
     return listener;
@@ -3342,6 +3354,7 @@
   return rv;
 };
 goog.json = {};
+goog.json.USE_NATIVE_JSON = !1;
 goog.json.isValid_ = function(s) {
   if (/^\s*$/.test(s)) {
     return!1;
@@ -3349,7 +3362,7 @@
   var backslashesRe = /\\["\\\/bfnrtu]/g, simpleValuesRe = /"[^"\\\n\r\u2028\u2029\x00-\x08\x0a-\x1f]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, openBracketsRe = /(?:^|:|,)(?:[\s\u2028\u2029]*\[)+/g, remainderRe = /^[\],:{}\s\u2028\u2029]*$/;
   return remainderRe.test(s.replace(backslashesRe, "@").replace(simpleValuesRe, "]").replace(openBracketsRe, ""));
 };
-goog.json.parse = function(s) {
+goog.json.parse = goog.json.USE_NATIVE_JSON ? goog.global.JSON.parse : function(s) {
   var o = String(s);
   if (goog.json.isValid_(o)) {
     try {
@@ -3359,10 +3372,10 @@
   }
   throw Error("Invalid JSON string: " + o);
 };
-goog.json.unsafeParse = function(s) {
+goog.json.unsafeParse = goog.json.USE_NATIVE_JSON ? goog.global.JSON.parse : function(s) {
   return eval("(" + s + ")");
 };
-goog.json.serialize = function(object, opt_replacer) {
+goog.json.serialize = goog.json.USE_NATIVE_JSON ? goog.global.JSON.stringify : function(object, opt_replacer) {
   return(new goog.json.Serializer(opt_replacer)).serialize(object);
 };
 goog.json.Serializer = function(opt_replacer) {
@@ -3535,10 +3548,8 @@
 goog.iter.map = function(iterable, f, opt_obj) {
   var iterator = goog.iter.toIterator(iterable), newIter = new goog.iter.Iterator;
   newIter.next = function() {
-    for (;;) {
-      var val = iterator.next();
-      return f.call(opt_obj, val, void 0, iterator);
-    }
+    var val = iterator.next();
+    return f.call(opt_obj, val, void 0, iterator);
   };
   return newIter;
 };
@@ -4241,8 +4252,8 @@
   return str.join("\n");
 };
 goog.debug.deepExpose = function(obj$$0, opt_showFn) {
-  var previous = new goog.structs.Set, str = [], helper = function(obj, space) {
-    var nestspace = space + "  ";
+  var str = [], helper = function(obj, space, parentSeen) {
+    var nestspace = space + "  ", seen = new goog.structs.Set(parentSeen);
     try {
       if (goog.isDef(obj)) {
         if (goog.isNull(obj)) {
@@ -4255,14 +4266,14 @@
               str.push(String(obj).replace(/\n/g, "\n" + space));
             } else {
               if (goog.isObject(obj)) {
-                if (previous.contains(obj)) {
+                if (seen.contains(obj)) {
                   str.push("*** reference loop detected ***");
                 } else {
-                  previous.add(obj);
+                  seen.add(obj);
                   str.push("{");
                   for (var x in obj) {
                     if (opt_showFn || !goog.isFunction(obj[x])) {
-                      str.push("\n"), str.push(nestspace), str.push(x + " = "), helper(obj[x], nestspace);
+                      str.push("\n"), str.push(nestspace), str.push(x + " = "), helper(obj[x], nestspace, seen);
                     }
                   }
                   str.push("\n" + space + "}");
@@ -4280,7 +4291,7 @@
       str.push("*** " + e + " ***");
     }
   };
-  helper(obj$$0, "");
+  helper(obj$$0, "", new goog.structs.Set);
   return str.join("");
 };
 goog.debug.exposeArray = function(arr) {
@@ -4356,7 +4367,7 @@
   } else {
     if (fn && visited.length < goog.debug.MAX_STACK_DEPTH) {
       sb.push(goog.debug.getFunctionName(fn) + "(");
-      for (var args = fn.arguments, i = 0;i < args.length;i++) {
+      for (var args = fn.arguments, i = 0;args && i < args.length;i++) {
         0 < i && sb.push(", ");
         var argDesc, arg = args[i];
         switch(typeof arg) {
@@ -4480,11 +4491,8 @@
 };
 goog.debug.Logger = function(name) {
   this.name_ = name;
+  this.handlers_ = this.children_ = this.level_ = this.parent_ = null;
 };
-goog.debug.Logger.prototype.parent_ = null;
-goog.debug.Logger.prototype.level_ = null;
-goog.debug.Logger.prototype.children_ = null;
-goog.debug.Logger.prototype.handlers_ = null;
 goog.debug.Logger.ENABLE_HIERARCHY = !0;
 goog.debug.Logger.ENABLE_HIERARCHY || (goog.debug.Logger.rootHandlers_ = []);
 goog.debug.Logger.Level = function(name, value) {
@@ -4663,7 +4671,7 @@
 goog.log.LogRecord = goog.debug.LogRecord;
 goog.log.getLogger = function(name, opt_level) {
   if (goog.log.ENABLED) {
-    var logger = goog.debug.Logger.getLogger(name);
+    var logger = goog.debug.LogManager.getLogger(name);
     opt_level && logger && logger.setLevel(opt_level);
     return logger;
   }
@@ -4960,6 +4968,11 @@
   goog.string.startsWith(path, "/") && (path = path.substr(1));
   return goog.string.buildString(baseUri, "/", path);
 };
+goog.uri.utils.setPath = function(uri, path) {
+  goog.string.startsWith(path, "/") || (path = "/" + path);
+  var parts = goog.uri.utils.split(uri);
+  return goog.uri.utils.buildFromEncodedParts(parts[goog.uri.utils.ComponentIndex.SCHEME], parts[goog.uri.utils.ComponentIndex.USER_INFO], parts[goog.uri.utils.ComponentIndex.DOMAIN], parts[goog.uri.utils.ComponentIndex.PORT], path, parts[goog.uri.utils.ComponentIndex.QUERY_DATA], parts[goog.uri.utils.ComponentIndex.FRAGMENT]);
+};
 goog.uri.utils.StandardQueryParam = {RANDOM:"zx"};
 goog.uri.utils.makeUnique = function(uri) {
   return goog.uri.utils.setParam(uri, goog.uri.utils.StandardQueryParam.RANDOM, goog.string.getRandomString());
@@ -5050,6 +5063,8 @@
   return goog.net.XmlHttp.factory_.createInstance();
 };
 goog.net.XmlHttp.ASSUME_NATIVE_XHR = !1;
+goog.net.XmlHttpDefines = {};
+goog.net.XmlHttpDefines.ASSUME_NATIVE_XHR = !1;
 goog.net.XmlHttp.getOptions = function() {
   return goog.net.XmlHttp.factory_.getOptions();
 };
@@ -5074,7 +5089,7 @@
   return options;
 };
 goog.net.DefaultXmlHttpFactory.prototype.getProgId_ = function() {
-  if (goog.net.XmlHttp.ASSUME_NATIVE_XHR) {
+  if (goog.net.XmlHttp.ASSUME_NATIVE_XHR || goog.net.XmlHttpDefines.ASSUME_NATIVE_XHR) {
     return "";
   }
   if (!this.ieProgId_ && "undefined" == typeof XMLHttpRequest && "undefined" != typeof ActiveXObject) {
@@ -5275,7 +5290,7 @@
   try {
     return this.getReadyState() > goog.net.XmlHttp.ReadyState.LOADED ? this.xhr_.status : -1;
   } catch (e) {
-    return goog.log.warning(this.logger_, "Can not get status: " + e.message), -1;
+    return-1;
   }
 };
 goog.net.XhrIo.prototype.getStatusText = function() {
diff --git a/google/appengine/tools/dev_appserver_apiserver.py b/google/appengine/tools/dev_appserver_apiserver.py
index d0e56f7..21ad1d3 100644
--- a/google/appengine/tools/dev_appserver_apiserver.py
+++ b/google/appengine/tools/dev_appserver_apiserver.py
@@ -155,12 +155,12 @@
   """
   API_PREFIX = '/_ah/api/'
 
-  def __init__(self, base_env_dict, dev_appserver, request=None):
+  def __init__(self, base_env_dict, old_dev_appserver, request=None):
     """Constructor.
 
     Args:
       base_env_dict: Dictionary of CGI environment parameters.
-      dev_appserver: used to call standard SplitURL method.
+      old_dev_appserver: used to call standard SplitURL method.
       request: AppServerRequest.  Can be None.
     """
     self.cgi_env = base_env_dict
@@ -168,7 +168,7 @@
     self.http_method = base_env_dict['REQUEST_METHOD']
     self.port = base_env_dict['SERVER_PORT']
     if request:
-      self.path, self.query = dev_appserver.SplitURL(request.relative_url)
+      self.path, self.query = old_dev_appserver.SplitURL(request.relative_url)
 
 
       self.body = request.infile.read()
@@ -804,9 +804,9 @@
 
 
 
-  from google.appengine.tools import dev_appserver
+  from google.appengine.tools import old_dev_appserver
 
-  class ApiserverDispatcher(dev_appserver.URLDispatcher):
+  class ApiserverDispatcher(old_dev_appserver.URLDispatcher):
     """Dispatcher that handles requests to the built-in apiserver handlers."""
 
     _API_EXPLORER_URL = 'http://apis-explorer.appspot.com/apis-explorer/?base='
@@ -833,7 +833,7 @@
                           self.HandleApiExplorerRequest)
       self._AddDispatcher('/_ah/api/static/.*$',
                           self.HandleApiStaticRequest)
-      dev_appserver.URLDispatcher.__init__(self, *args, **kwargs)
+      old_dev_appserver.URLDispatcher.__init__(self, *args, **kwargs)
 
     def _AddDispatcher(self, path_regex, dispatch_function):
       """Add a request path and dispatch handler.
@@ -915,7 +915,7 @@
         return None
 
 
-      self.request = ApiRequest(base_env_dict, dev_appserver, request)
+      self.request = ApiRequest(base_env_dict, old_dev_appserver, request)
 
 
 
@@ -925,7 +925,7 @@
 
 
       self._request_stage = self.RequestState.GET_API_CONFIGS
-      return self.GetApiConfigs(base_env_dict, dev_appserver)
+      return self.GetApiConfigs(base_env_dict, old_dev_appserver)
 
     def HandleApiExplorerRequest(self, unused_request, outfile, base_env_dict):
       """Handler for requests to _ah/api/explorer.
@@ -1007,20 +1007,21 @@
       else:
         return self.FailRequest('EndRedirect in unexpected state', outfile)
 
-    def GetApiConfigs(self, cgi_env, dev_appserver):
+    def GetApiConfigs(self, cgi_env, old_dev_appserver):
       """Makes a call to BackendService.getApiConfigs and parses result.
 
       Args:
         cgi_env: CGI environment dictionary as passed in by the framework
-        dev_appserver: dev_appserver instance used to generate AppServerRequest.
+        old_dev_appserver:
+            old_dev_appserver instance used to generate AppServerRequest.
 
       Returns:
         AppServerRequest to be returned as an internal redirect to getApiConfigs
       """
-      request = ApiRequest(cgi_env, dev_appserver)
+      request = ApiRequest(cgi_env, old_dev_appserver)
       request.path = 'BackendService.getApiConfigs'
       request.body = '{}'
-      return BuildCGIRequest(cgi_env, request, dev_appserver)
+      return BuildCGIRequest(cgi_env, request, old_dev_appserver)
 
     @staticmethod
     def VerifyResponse(response, status_code, content_type=None):
@@ -1075,7 +1076,7 @@
       Returns:
         True on success, False on failure
       """
-      response = dev_appserver.RewriteResponse(dispatched_output)
+      response = old_dev_appserver.RewriteResponse(dispatched_output)
       if self.VerifyResponse(response, 200, 'application/json'):
         self.config_manager.ParseApiConfigResponse(response.body.read())
         return True
@@ -1109,7 +1110,7 @@
           if not discovery_service.HandleDiscoveryRequest(self.request.path):
             self._request_stage = self.RequestState.SPI_CALL
             return BuildCGIRequest(self.request.cgi_env, self.request,
-                                   dev_appserver)
+                                   old_dev_appserver)
         except RequestRejectionError, rejection_error:
           self._EndRequest()
           return SendCGIRejectedResponse(rejection_error, outfile)
@@ -1169,7 +1170,7 @@
         None
       """
 
-      response = dev_appserver.AppServerResponse(
+      response = old_dev_appserver.AppServerResponse(
           response_file=dispatched_output)
       response_headers, body = self.ParseCgiResponse(response)
 
@@ -1498,16 +1499,16 @@
   return ApiserverDispatcher(config_manager)
 
 
-def BuildCGIRequest(base_env_dict, request, dev_appserver):
+def BuildCGIRequest(base_env_dict, request, old_dev_appserver):
   """Build a CGI request to Call a method on an SPI backend.
 
   Args:
     base_env_dict: CGI environment dict
     request: ApiRequest to be converted to a CGI request
-    dev_appserver: Handle to dev_appserver to generate CGI request.
+    old_dev_appserver: Handle to old_dev_appserver to generate CGI request.
 
   Returns:
-    dev_appserver.AppServerRequest internal redirect object
+    old_dev_appserver.AppServerRequest internal redirect object
   """
   if request.headers is None:
     request.headers = {}
@@ -1528,7 +1529,7 @@
   body_outfile.write(request.body)
   header_outfile.seek(0)
   body_outfile.seek(0)
-  return dev_appserver.AppServerRequest(
+  return old_dev_appserver.AppServerRequest(
       url, None, mimetools.Message(header_outfile), body_outfile)
 
 
diff --git a/google/appengine/tools/dev_appserver_blobimage.py b/google/appengine/tools/dev_appserver_blobimage.py
index c83a467..d155138 100644
--- a/google/appengine/tools/dev_appserver_blobimage.py
+++ b/google/appengine/tools/dev_appserver_blobimage.py
@@ -62,9 +62,9 @@
 
 
 
-  from google.appengine.tools import dev_appserver
+  from google.appengine.tools import old_dev_appserver
 
-  class BlobImageDispatcher(dev_appserver.URLDispatcher):
+  class BlobImageDispatcher(old_dev_appserver.URLDispatcher):
     """Dispatcher that handles image serving requests."""
 
     _size_limit = 1600
diff --git a/google/appengine/tools/dev_appserver_blobstore.py b/google/appengine/tools/dev_appserver_blobstore.py
index fce2e54..312925b 100644
--- a/google/appengine/tools/dev_appserver_blobstore.py
+++ b/google/appengine/tools/dev_appserver_blobstore.py
@@ -300,9 +300,9 @@
   """
 
 
-  from google.appengine.tools import dev_appserver
+  from google.appengine.tools import old_dev_appserver
 
-  class UploadDispatcher(dev_appserver.URLDispatcher):
+  class UploadDispatcher(old_dev_appserver.URLDispatcher):
     """Dispatcher that handles uploads."""
 
     def __init__(self):
@@ -377,7 +377,7 @@
                               'Content-Length: %d\r\n'
                               '\r\n') % (header_text, len(content_text))
 
-          return dev_appserver.AppServerRequest(
+          return old_dev_appserver.AppServerRequest(
               success_path,
               None,
               mimetools.Message(cStringIO.StringIO(complete_headers)),
@@ -411,7 +411,7 @@
       Makes sure the application upload handler returned an appropriate status
       code.
       """
-      response = dev_appserver.RewriteResponse(dispatched_output)
+      response = old_dev_appserver.RewriteResponse(dispatched_output)
       logging.info('Upload handler returned %d', response.status_code)
       outfile = cStringIO.StringIO()
       outfile.write('Status: %s\n' % response.status_code)
@@ -423,8 +423,8 @@
         outfile.write(''.join(response.headers.headers))
 
       outfile.seek(0)
-      dev_appserver.URLDispatcher.EndRedirect(self,
-                                              outfile,
-                                              original_output)
+      old_dev_appserver.URLDispatcher.EndRedirect(self,
+                                                  outfile,
+                                                  original_output)
 
   return UploadDispatcher()
diff --git a/google/appengine/tools/dev_appserver_channel.py b/google/appengine/tools/dev_appserver_channel.py
index 6e49bca..2442f27 100644
--- a/google/appengine/tools/dev_appserver_channel.py
+++ b/google/appengine/tools/dev_appserver_channel.py
@@ -58,9 +58,9 @@
 
 
 
-  from google.appengine.tools import dev_appserver
+  from google.appengine.tools import old_dev_appserver
 
-  class ChannelDispatcher(dev_appserver.URLDispatcher):
+  class ChannelDispatcher(old_dev_appserver.URLDispatcher):
     """Dispatcher that handles channel polls."""
 
     def __init__(self, channel_service_stub):
diff --git a/google/appengine/tools/dev_appserver_main.py b/google/appengine/tools/dev_appserver_main.py
index 7f6667c..cac350a 100644
--- a/google/appengine/tools/dev_appserver_main.py
+++ b/google/appengine/tools/dev_appserver_main.py
@@ -172,7 +172,7 @@
 from google.appengine.dist import py_zipimport
 from google.appengine.tools import appcfg
 from google.appengine.tools import appengine_rpc
-from google.appengine.tools import dev_appserver
+from google.appengine.tools import old_dev_appserver
 from google.appengine.tools import dev_appserver_multiprocess as multiprocess
 from google.appengine.tools import sdk_update_checker
 
@@ -623,10 +623,10 @@
 
   if '_DEFAULT_ENV_AUTH_DOMAIN' in option_dict:
     auth_domain = option_dict['_DEFAULT_ENV_AUTH_DOMAIN']
-    dev_appserver.DEFAULT_ENV['AUTH_DOMAIN'] = auth_domain
+    old_dev_appserver.DEFAULT_ENV['AUTH_DOMAIN'] = auth_domain
   if '_ENABLE_LOGGING' in option_dict:
     enable_logging = option_dict['_ENABLE_LOGGING']
-    dev_appserver.HardenedModulesHook.ENABLE_LOGGING = enable_logging
+    old_dev_appserver.HardenedModulesHook.ENABLE_LOGGING = enable_logging
 
   log_level = option_dict[ARG_LOG_LEVEL]
 
@@ -640,12 +640,12 @@
   default_partition = option_dict[ARG_DEFAULT_PARTITION]
   appinfo = None
   try:
-    appinfo, _, _ = dev_appserver.LoadAppConfig(
+    appinfo, _, _ = old_dev_appserver.LoadAppConfig(
         root_path, {}, default_partition=default_partition)
   except yaml_errors.EventListenerError, e:
     logging.error('Fatal error when loading application configuration:\n%s', e)
     return 1
-  except dev_appserver.InvalidAppConfigError, e:
+  except old_dev_appserver.InvalidAppConfigError, e:
     logging.error('Application configuration file invalid:\n%s', e)
     return 1
 
@@ -702,9 +702,9 @@
     logging.getLogger().setLevel(logging.WARNING)
 
   try:
-    dev_appserver.SetupStubs(appinfo.application,
-                             _use_atexit_for_datastore_stub=True,
-                             **option_dict)
+    old_dev_appserver.SetupStubs(appinfo.application,
+                                 _use_atexit_for_datastore_stub=True,
+                                 **option_dict)
   except:
     exc_type, exc_value, exc_traceback = sys.exc_info()
     logging.error(str(exc_type) + ': ' + str(exc_value))
@@ -715,7 +715,7 @@
   frontend_port=option_dict.get(ARG_MULTIPROCESS_FRONTEND_PORT, None)
   if frontend_port is not None:
     frontend_port = int(frontend_port)
-  http_server = dev_appserver.CreateServer(
+  http_server = old_dev_appserver.CreateServer(
       root_path,
       login_url,
       port,
@@ -755,7 +755,7 @@
         done = True
       except KeyboardInterrupt:
         pass
-    dev_appserver.TearDownStubs()
+    old_dev_appserver.TearDownStubs()
 
   return 0
 
diff --git a/google/appengine/tools/dev_appserver_oauth.py b/google/appengine/tools/dev_appserver_oauth.py
index 16f2e69..7f5d3e3 100644
--- a/google/appengine/tools/dev_appserver_oauth.py
+++ b/google/appengine/tools/dev_appserver_oauth.py
@@ -217,9 +217,9 @@
 
 
 
-  from google.appengine.tools import dev_appserver
+  from google.appengine.tools import old_dev_appserver
 
-  class OAuthDispatcher(dev_appserver.URLDispatcher):
+  class OAuthDispatcher(old_dev_appserver.URLDispatcher):
     """Dispatcher that handles requests to the built-in OAuth handlers."""
 
     def Dispatch(self,
@@ -254,7 +254,7 @@
         body or query string (in the form of {key :[value1, value2]}).
       """
       method = base_env_dict['REQUEST_METHOD']
-      path, query = dev_appserver.SplitURL(request.relative_url)
+      path, query = old_dev_appserver.SplitURL(request.relative_url)
       parameters = {}
       if method == 'POST':
         form = cgi.FieldStorage(fp=request.infile,
diff --git a/google/appengine/tools/devappserver2/admin/datastore_viewer.py b/google/appengine/tools/devappserver2/admin/datastore_viewer.py
index 0515a86..452ffdd 100644
--- a/google/appengine/tools/devappserver2/admin/datastore_viewer.py
+++ b/google/appengine/tools/devappserver2/admin/datastore_viewer.py
@@ -336,7 +336,17 @@
     return bool(int(value))
 
 
-class IntType(DataType):
+class NumberType(DataType):
+
+  def input_field(self, name, value, sample_values, back_uri):
+    string_value = self.format(value) if value is not None else ''
+    return super(NumberType, self).input_field(name,
+                                               string_value,
+                                               sample_values,
+                                               back_uri)
+
+
+class IntType(NumberType):
   PLACEHOLDER = '42'
 
   def input_field_size(self):
@@ -349,7 +359,7 @@
     return int(value)
 
 
-class FloatType(DataType):
+class FloatType(NumberType):
   PLACEHOLDER = '3.14159'
 
   def name(self):
diff --git a/google/appengine/tools/devappserver2/admin/datastore_viewer_test.py b/google/appengine/tools/devappserver2/admin/datastore_viewer_test.py
index b41a130..5876e72 100644
--- a/google/appengine/tools/devappserver2/admin/datastore_viewer_test.py
+++ b/google/appengine/tools/devappserver2/admin/datastore_viewer_test.py
@@ -714,6 +714,13 @@
     self.entity4['listprop'] = [10, 11]
     datastore.Put(self.entity4)
 
+    self.entity5 = datastore.Entity('Kind1', id=127, _app=self.app_id)
+    self.entity5['intprop'] = 0
+    self.entity5['boolprop'] = False
+    self.entity5['stringprop'] = ''
+    self.entity5['floatprop'] = 0.0
+    datastore.Put(self.entity5)
+
   def tearDown(self):
     self.mox.UnsetStubs()
 
@@ -725,10 +732,16 @@
 
     handler.render(
         'datastore_edit.html',
-        {'fields': [('dateprop',
+        {'fields': [('boolprop',
+                     'bool',
+                     mox.Regex('^<select class="bool"(.|\n)*$')),
+                    ('dateprop',
                      'overflowdatetime',
                      mox.Regex('^<input class="overflowdatetime".*'
                                'value="".*$')),
+                    ('floatprop',
+                     'float',
+                     mox.Regex('^<input class="float".*value="".*$')),
                     ('intprop',
                      'int',
                      mox.Regex('^<input class="int".*value="".*$')),
@@ -793,6 +806,39 @@
     handler.get(str(self.entity1.key()))
     self.mox.VerifyAll()
 
+  def test_get_entity_zero_props(self):
+    request = webapp2.Request.blank(
+        '/datastore/edit/%s?next=http://next/' % self.entity5.key())
+    response = webapp2.Response()
+    handler = datastore_viewer.DatastoreEditRequestHandler(request, response)
+
+    handler.render(
+        'datastore_edit.html',
+        {'fields': [('boolprop',
+                     'bool',
+                     mox.Regex('^<select class="bool"(.|\n)*$')),
+                    ('floatprop',
+                     'float',
+                     mox.Regex('^<input class="float".*value="0\.0".*$')),
+                    ('intprop',
+                     'int',
+                     mox.Regex('^<input class="int".*value="0".*$')),
+                    ('stringprop',
+                     'string',
+                     mox.Regex('^<input class="string".*value="".*$'))],
+         'key': str(self.entity5.key()),
+         'key_id': 127,
+         'key_name': None,
+         'kind': 'Kind1',
+         'namespace': '',
+         'next': 'http://next/',
+         'parent_key': None,
+         'parent_key_string': None})
+
+    self.mox.ReplayAll()
+    handler.get(str(self.entity5.key()))
+    self.mox.VerifyAll()
+
   def test_post_no_entity_key_string(self):
     request = webapp2.Request.blank(
         '/datastore/edit',
diff --git a/google/appengine/tools/devappserver2/application_configuration.py b/google/appengine/tools/devappserver2/application_configuration.py
index 48079df..23f1913 100644
--- a/google/appengine/tools/devappserver2/application_configuration.py
+++ b/google/appengine/tools/devappserver2/application_configuration.py
@@ -32,6 +32,7 @@
 from google.appengine.api import appinfo_includes
 from google.appengine.api import backendinfo
 from google.appengine.api import dispatchinfo
+from google.appengine.tools import yaml_translator
 from google.appengine.tools.devappserver2 import errors
 
 # Constants passed to functions registered with
@@ -45,6 +46,12 @@
 NOBUILD_FILES_CHANGED = 7
 
 
+def java_supported():
+  """True if this SDK supports running Java apps in the dev appserver."""
+  java_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'java')
+  return os.path.isdir(java_dir)
+
+
 class ModuleConfiguration(object):
   """Stores module configuration information.
 
@@ -66,21 +73,29 @@
       ('manual_scaling', 'manual_scaling'),
       ('automatic_scaling', 'automatic_scaling')]
 
-  def __init__(self, yaml_path):
+  def __init__(self, config_path):
     """Initializer for ModuleConfiguration.
 
     Args:
-      yaml_path: A string containing the full path of the yaml file containing
-          the configuration for this module.
+      config_path: A string containing the full path of the yaml or xml file
+          containing the configuration for this module.
     """
-    self._yaml_path = yaml_path
-    self._app_info_external = None
-    self._application_root = os.path.realpath(os.path.dirname(yaml_path))
+    self._config_path = config_path
+    root = os.path.dirname(config_path)
+    self._is_java = os.path.normpath(config_path).endswith(
+        os.sep + 'WEB-INF' + os.sep + 'appengine-web.xml')
+    if self._is_java:
+      # We assume Java's XML-based config files only if config_path is
+      # something like /foo/bar/WEB-INF/appengine-web.xml. In this case,
+      # the application root is /foo/bar. Other apps, configured with YAML,
+      # have something like /foo/bar/app.yaml, with application root /foo/bar.
+      root = os.path.dirname(root)
+    self._application_root = os.path.realpath(root)
     self._last_failure_message = None
 
     self._app_info_external, files_to_check = self._parse_configuration(
-        self._yaml_path)
-    self._mtimes = self._get_mtimes([self._yaml_path] + files_to_check)
+        self._config_path)
+    self._mtimes = self._get_mtimes(files_to_check)
     self._application = 'dev~%s' % self._app_info_external.application
     self._api_version = self._app_info_external.api_version
     self._module_name = self._app_info_external.module
@@ -96,7 +111,7 @@
           '"python27" runtime will be used instead. A description of the '
           'differences between the two can be found here:\n'
           'https://developers.google.com/appengine/docs/python/python25/diff27',
-           self._yaml_path)
+          self._config_path)
     self._minor_version_id = ''.join(random.choice(string.digits) for _ in
                                      range(18))
 
@@ -198,7 +213,7 @@
 
     try:
       app_info_external, files_to_check = self._parse_configuration(
-          self._yaml_path)
+          self._config_path)
     except Exception, e:
       failure_message = str(e)
       if failure_message != self._last_failure_message:
@@ -207,7 +222,7 @@
       return set()
     self._last_failure_message = None
 
-    self._mtimes = self._get_mtimes([self._yaml_path] + files_to_check)
+    self._mtimes = self._get_mtimes(files_to_check)
 
     for app_info_attribute, self_attribute in self._IMMUTABLE_PROPERTIES:
       app_info_value = getattr(app_info_external, app_info_attribute)
@@ -264,30 +279,71 @@
           raise
     return filename_to_mtime
 
+  def _parse_configuration(self, configuration_path):
+    """Parse a configuration file (like app.yaml or appengine-web.xml).
+
+    Args:
+      configuration_path: A string containing the full path of the yaml file
+          containing the configuration for this module.
+
+    Returns:
+      A tuple where the first element is the parsed appinfo.AppInfoExternal
+      object and the second element is a list of the paths of the files that
+      were used to produce it, namely the input configuration_path and any
+      other file that was included from that one.
+    """
+    if self._is_java:
+      config, files = self._parse_java_configuration(configuration_path)
+    else:
+      with open(configuration_path) as f:
+        config, files = appinfo_includes.ParseAndReturnIncludePaths(f)
+    return config, [configuration_path] + files
+
   @staticmethod
-  def _parse_configuration(configuration_path):
-    # TODO: It probably makes sense to catch the exception raised
-    # by Parse() and re-raise it using a module-specific exception.
-    with open(configuration_path) as f:
-      return appinfo_includes.ParseAndReturnIncludePaths(f)
+  def _parse_java_configuration(app_engine_web_xml_path):
+    """Parse appengine-web.xml and web.xml.
+
+    Args:
+      app_engine_web_xml_path: A string containing the full path of the
+          .../WEB-INF/appengine-web.xml file. The corresponding
+          .../WEB-INF/web.xml file must also be present.
+
+    Returns:
+      A tuple where the first element is the parsed appinfo.AppInfoExternal
+      object and the second element is a list of the paths of the files that
+      were used to produce it, namely the input appengine-web.xml file and the
+      corresponding web.xml file.
+    """
+    with open(app_engine_web_xml_path) as f:
+      app_engine_web_xml_str = f.read()
+    web_inf_dir = os.path.dirname(app_engine_web_xml_path)
+    web_xml_path = os.path.join(web_inf_dir, 'web.xml')
+    with open(web_xml_path) as f:
+      web_xml_str = f.read()
+    static_files = []
+    # TODO: need to enumerate static files here
+    app_yaml_str = yaml_translator.TranslateXmlToYaml(
+        app_engine_web_xml_str, web_xml_str, static_files)
+    config = appinfo.LoadSingleAppInfo(app_yaml_str)
+    return config, [app_engine_web_xml_path, web_xml_path]
 
 
 class BackendsConfiguration(object):
   """Stores configuration information for a backends.yaml file."""
 
-  def __init__(self, app_yaml_path, backend_yaml_path):
+  def __init__(self, app_config_path, backend_config_path):
     """Initializer for BackendsConfiguration.
 
     Args:
-      app_yaml_path: A string containing the full path of the yaml file
+      app_config_path: A string containing the full path of the yaml file
           containing the configuration for this module.
-      backend_yaml_path: A string containing the full path of the backends.yaml
-          file containing the configuration for backends.
+      backend_config_path: A string containing the full path of the
+          backends.yaml file containing the configuration for backends.
     """
     self._update_lock = threading.RLock()
-    self._base_module_configuration = ModuleConfiguration(app_yaml_path)
+    self._base_module_configuration = ModuleConfiguration(app_config_path)
     backend_info_external = self._parse_configuration(
-        backend_yaml_path)
+        backend_config_path)
 
     self._backends_name_to_backend_entry = {}
     for backend in backend_info_external.backends or []:
@@ -463,10 +519,10 @@
 class DispatchConfiguration(object):
   """Stores dispatcher configuration information."""
 
-  def __init__(self, yaml_path):
-    self._yaml_path = yaml_path
-    self._mtime = os.path.getmtime(self._yaml_path)
-    self._process_dispatch_entries(self._parse_configuration(self._yaml_path))
+  def __init__(self, config_path):
+    self._config_path = config_path
+    self._mtime = os.path.getmtime(self._config_path)
+    self._process_dispatch_entries(self._parse_configuration(self._config_path))
 
   @staticmethod
   def _parse_configuration(configuration_path):
@@ -476,11 +532,11 @@
       return dispatchinfo.LoadSingleDispatch(f)
 
   def check_for_updates(self):
-    mtime = os.path.getmtime(self._yaml_path)
+    mtime = os.path.getmtime(self._config_path)
     if mtime > self._mtime:
       self._mtime = mtime
       try:
-        dispatch_info_external = self._parse_configuration(self._yaml_path)
+        dispatch_info_external = self._parse_configuration(self._config_path)
       except Exception, e:
         failure_message = str(e)
         logging.error('Configuration is not valid: %s', failure_message)
@@ -511,49 +567,36 @@
 class ApplicationConfiguration(object):
   """Stores application configuration information."""
 
-  def __init__(self, yaml_paths):
+  def __init__(self, config_paths):
     """Initializer for ApplicationConfiguration.
 
     Args:
-      yaml_paths: A list of strings containing the paths to yaml files.
+      config_paths: A list of strings containing the paths to yaml files,
+          or to directories containing them.
     """
     self.modules = []
     self.dispatch = None
-    if len(yaml_paths) == 1 and os.path.isdir(yaml_paths[0]):
-      directory_path = yaml_paths[0]
-      for app_yaml_path in [os.path.join(directory_path, 'app.yaml'),
-                            os.path.join(directory_path, 'app.yml')]:
-        if os.path.exists(app_yaml_path):
-          yaml_paths = [app_yaml_path]
-          break
-      else:
-        raise errors.AppConfigNotFoundError(
-            'no app.yaml file at %r' % directory_path)
-      for backends_yaml_path in [os.path.join(directory_path, 'backends.yaml'),
-                                 os.path.join(directory_path, 'backends.yml')]:
-        if os.path.exists(backends_yaml_path):
-          yaml_paths.append(backends_yaml_path)
-          break
-    for yaml_path in yaml_paths:
-      if os.path.isdir(yaml_path):
-        raise errors.InvalidAppConfigError(
-            '"%s" is a directory and a yaml configuration file is required' %
-            yaml_path)
-      elif (yaml_path.endswith('backends.yaml') or
-            yaml_path.endswith('backends.yml')):
+    # It's really easy to add a test case that passes in a string rather than
+    # a list of strings, so guard against that.
+    assert not isinstance(config_paths, basestring)
+    config_paths = self._config_files_from_paths(config_paths)
+    for config_path in config_paths:
+      # TODO: add support for backends.xml and dispatch.xml here
+      if (config_path.endswith('backends.yaml') or
+          config_path.endswith('backends.yml')):
         # TODO: Reuse the ModuleConfiguration created for the app.yaml
         # instead of creating another one for the same file.
         self.modules.extend(
-            BackendsConfiguration(yaml_path.replace('backends.y', 'app.y'),
-                                  yaml_path).get_backend_configurations())
-      elif (yaml_path.endswith('dispatch.yaml') or
-            yaml_path.endswith('dispatch.yml')):
+            BackendsConfiguration(config_path.replace('backends.y', 'app.y'),
+                                  config_path).get_backend_configurations())
+      elif (config_path.endswith('dispatch.yaml') or
+            config_path.endswith('dispatch.yml')):
         if self.dispatch:
           raise errors.InvalidAppConfigError(
               'Multiple dispatch.yaml files specified')
-        self.dispatch = DispatchConfiguration(yaml_path)
+        self.dispatch = DispatchConfiguration(config_path)
       else:
-        module_configuration = ModuleConfiguration(yaml_path)
+        module_configuration = ModuleConfiguration(config_path)
         self.modules.append(module_configuration)
     application_ids = set(module.application
                           for module in self.modules)
@@ -581,6 +624,97 @@
             'Modules %s specified in dispatch.yaml are not defined by a yaml '
             'file.' % sorted(missing_modules))
 
+  def _config_files_from_paths(self, config_paths):
+    """Return a list of the configuration files found in the given paths.
+
+    For any path that is a directory, the returned list will contain the
+    configuration files (app.yaml and optionally backends.yaml) found in that
+    directory. If the directory is a Java app (contains a subdirectory
+    WEB-INF with web.xml and application-web.xml files), then the returned
+    list will contain the path to the application-web.xml file, which is treated
+    as if it included web.xml. Paths that are not directories are added to the
+    returned list as is.
+
+    Args:
+      config_paths: a list of strings that are file or directory paths.
+
+    Returns:
+      A list of strings that are file paths.
+    """
+    config_files = []
+    for path in config_paths:
+      config_files += (
+          self._config_files_from_dir(path) if os.path.isdir(path) else [path])
+    return config_files
+
+  def _config_files_from_dir(self, dir_path):
+    """Return a list of the configuration files found in the given directory.
+
+    If the directory contains a subdirectory WEB-INF then we expect to find
+    web.xml and application-web.xml in that subdirectory. The returned list
+    will consist of the path to application-web.xml, which we treat as if it
+    included xml.
+
+    Otherwise, we expect to find an app.yaml and optionally a backends.yaml,
+    and we return those in the list.
+
+    Args:
+      dir_path: a string that is the path to a directory.
+
+    Returns:
+      A list of strings that are file paths.
+    """
+    web_inf = os.path.join(dir_path, 'WEB-INF')
+    if java_supported() and os.path.isdir(web_inf):
+      return self._config_files_from_web_inf_dir(web_inf)
+    app_yamls = self._files_in_dir_matching(dir_path, ['app.yaml', 'app.yml'])
+    if not app_yamls:
+      or_web_inf = ' or a WEB-INF subdirectory' if java_supported() else ''
+      raise errors.AppConfigNotFoundError(
+          '"%s" is a directory but does not contain app.yaml or app.yml%s' %
+          (dir_path, or_web_inf))
+    backend_yamls = self._files_in_dir_matching(
+        dir_path, ['backends.yaml', 'backends.yml'])
+    return app_yamls + backend_yamls
+
+  def _config_files_from_web_inf_dir(self, web_inf):
+    required = ['appengine-web.xml', 'web.xml']
+    missing = [f for f in required
+               if not os.path.exists(os.path.join(web_inf, f))]
+    if missing:
+      raise errors.AppConfigNotFoundError(
+          'The "%s" subdirectory exists but is missing %s' %
+          (web_inf, ' and '.join(missing)))
+    return [os.path.join(web_inf, required[0])]
+
+  @staticmethod
+  def _files_in_dir_matching(dir_path, names):
+    abs_names = [os.path.join(dir_path, name) for name in names]
+    files = [f for f in abs_names if os.path.exists(f)]
+    if len(files) > 1:
+      raise errors.InvalidAppConfigError(
+          'Directory "%s" contains %s' % (dir_path, ' and '.join(names)))
+    return files
+
   @property
   def app_id(self):
     return self._app_id
+
+
+def get_app_error_file(module_configuration):
+  """Returns application specific file to handle errors.
+
+  Dev AppServer only supports 'default' error code.
+
+  Args:
+    module_configuration: ModuleConfiguration.
+
+  Returns:
+      A string containing full path to error handler file or
+      None if no 'default' error handler is specified.
+  """
+  for error_handler in module_configuration.error_handlers or []:
+    if not error_handler.error_code or error_handler.error_code == 'default':
+      return os.path.join(module_configuration.application_root,
+                          error_handler.file)
+  return None
diff --git a/google/appengine/tools/devappserver2/application_configuration_test.py b/google/appengine/tools/devappserver2/application_configuration_test.py
index 7cd7071..baee636 100644
--- a/google/appengine/tools/devappserver2/application_configuration_test.py
+++ b/google/appengine/tools/devappserver2/application_configuration_test.py
@@ -18,7 +18,10 @@
 
 
 import collections
+from contextlib import contextmanager
 import os.path
+import shutil
+import tempfile
 import unittest
 
 import google
@@ -31,6 +34,20 @@
 from google.appengine.tools.devappserver2 import errors
 
 
+@contextmanager
+def _java_temporarily_supported():
+  """Make the java_supported() function return True temporarily.
+
+   Use as:
+     with _java_temporarily_supported():
+       ...test that relies on Java being supported...
+  """
+  old_java_supported = application_configuration.java_supported
+  application_configuration.java_supported = lambda: True
+  yield
+  application_configuration.java_supported = old_java_supported
+
+
 class TestModuleConfiguration(unittest.TestCase):
   """Tests for application_configuration.ModuleConfiguration."""
 
@@ -66,7 +83,7 @@
         env_variables=env_variables,
         )
     application_configuration.ModuleConfiguration._parse_configuration(
-        '/appdir/app.yaml').AndReturn((info, []))
+        '/appdir/app.yaml').AndReturn((info, ['/appdir/app.yaml']))
     os.path.getmtime('/appdir/app.yaml').AndReturn(10)
 
     self.mox.ReplayAll()
@@ -99,7 +116,7 @@
         runtime='python27',
         threadsafe=False)
     application_configuration.ModuleConfiguration._parse_configuration(
-        '/appdir/app.yaml').AndReturn((info, []))
+        '/appdir/app.yaml').AndReturn((info, ['/appdir/app.yaml']))
     os.path.getmtime('/appdir/app.yaml').AndReturn(10)
     os.path.getmtime('/appdir/app.yaml').AndReturn(10)
 
@@ -117,14 +134,16 @@
         includes=['/appdir/include.yaml'],
         threadsafe=False)
     application_configuration.ModuleConfiguration._parse_configuration(
-        '/appdir/app.yaml').AndReturn((info, ['/appdir/include.yaml']))
+        '/appdir/app.yaml').AndReturn(
+            (info, ['/appdir/app.yaml', '/appdir/include.yaml']))
     os.path.getmtime('/appdir/app.yaml').InAnyOrder().AndReturn(10)
     os.path.getmtime('/appdir/include.yaml').InAnyOrder().AndReturn(10)
     os.path.getmtime('/appdir/app.yaml').AndReturn(10)
     os.path.getmtime('/appdir/include.yaml').AndReturn(11)
 
     application_configuration.ModuleConfiguration._parse_configuration(
-        '/appdir/app.yaml').AndReturn((info, ['/appdir/include.yaml']))
+        '/appdir/app.yaml').AndReturn(
+            (info, ['/appdir/app.yaml', '/appdir/include.yaml']))
     os.path.getmtime('/appdir/app.yaml').InAnyOrder().AndReturn(10)
     os.path.getmtime('/appdir/include.yaml').InAnyOrder().AndReturn(11)
 
@@ -147,11 +166,11 @@
         runtime='python27',
         threadsafe=False)
     application_configuration.ModuleConfiguration._parse_configuration(
-        '/appdir/app.yaml').AndReturn((info, []))
+        '/appdir/app.yaml').AndReturn((info, ['/appdir/app.yaml']))
     os.path.getmtime('/appdir/app.yaml').AndReturn(10)
     os.path.getmtime('/appdir/app.yaml').AndReturn(11)
     application_configuration.ModuleConfiguration._parse_configuration(
-        '/appdir/app.yaml').AndReturn((info, []))
+        '/appdir/app.yaml').AndReturn((info, ['/appdir/app.yaml']))
     os.path.getmtime('/appdir/app.yaml').AndReturn(11)
 
     self.mox.ReplayAll()
@@ -187,11 +206,11 @@
             max_idle_instances=2))
 
     application_configuration.ModuleConfiguration._parse_configuration(
-        '/appdir/app.yaml').AndReturn((info1, []))
+        '/appdir/app.yaml').AndReturn((info1, ['/appdir/app.yaml']))
     os.path.getmtime('/appdir/app.yaml').AndReturn(10)
     os.path.getmtime('/appdir/app.yaml').AndReturn(11)
     application_configuration.ModuleConfiguration._parse_configuration(
-        '/appdir/app.yaml').AndReturn((info2, []))
+        '/appdir/app.yaml').AndReturn((info2, ['/appdir/app.yaml']))
     os.path.getmtime('/appdir/app.yaml').AndReturn(11)
 
     self.mox.ReplayAll()
@@ -234,11 +253,11 @@
         )
 
     application_configuration.ModuleConfiguration._parse_configuration(
-        '/appdir/app.yaml').AndReturn((info1, []))
+        '/appdir/app.yaml').AndReturn((info1, ['/appdir/app.yaml']))
     os.path.getmtime('/appdir/app.yaml').AndReturn(10)
     os.path.getmtime('/appdir/app.yaml').AndReturn(11)
     application_configuration.ModuleConfiguration._parse_configuration(
-        '/appdir/app.yaml').AndReturn((info2, []))
+        '/appdir/app.yaml').AndReturn((info2, ['/appdir/app.yaml']))
     os.path.getmtime('/appdir/app.yaml').AndReturn(11)
 
     self.mox.ReplayAll()
@@ -491,7 +510,7 @@
         options='public')
 
     application_configuration.ModuleConfiguration._parse_configuration(
-        '/appdir/app.yaml').AndReturn((info, []))
+        '/appdir/app.yaml').AndReturn((info, ['/appdir/app.yaml']))
     os.path.getmtime('/appdir/app.yaml').AndReturn(10)
 
     self.mox.ReplayAll()
@@ -562,7 +581,7 @@
         start='handler')
 
     application_configuration.ModuleConfiguration._parse_configuration(
-        '/appdir/app.yaml').AndReturn((info, []))
+        '/appdir/app.yaml').AndReturn((info, ['/appdir/app.yaml']))
     os.path.getmtime('/appdir/app.yaml').AndReturn(10)
 
     self.mox.ReplayAll()
@@ -627,235 +646,340 @@
 
   def setUp(self):
     self.mox = mox.Mox()
-    self.mox.StubOutWithMock(os.path, 'isdir')
-    self.mox.StubOutWithMock(os.path, 'getmtime')
-    self.mox.StubOutWithMock(os.path, 'exists')
     self.mox.StubOutWithMock(application_configuration, 'ModuleConfiguration')
     self.mox.StubOutWithMock(application_configuration, 'BackendsConfiguration')
     self.mox.StubOutWithMock(application_configuration, 'DispatchConfiguration')
+    self.tmpdir = tempfile.mkdtemp(dir=os.getenv('TEST_TMPDIR'))
 
   def tearDown(self):
     self.mox.UnsetStubs()
+    shutil.rmtree(self.tmpdir)
+
+  def _make_file_hierarchy(self, filenames):
+    absnames = []
+    for filename in filenames:
+      absname = os.path.normpath(self.tmpdir + '/' + filename)
+      absnames += [absname]
+      dirname = os.path.dirname(absname)
+      if not os.path.exists(dirname):
+        os.makedirs(dirname)
+      open(absname, 'w').close()
+    return absnames
 
   def test_yaml_files(self):
-    os.path.isdir('/appdir/app.yaml').AndReturn(False)
+    absnames = self._make_file_hierarchy(
+        ['appdir/app.yaml', 'appdir/other.yaml'])
+
     module_config1 = ModuleConfigurationStub()
     application_configuration.ModuleConfiguration(
-        '/appdir/app.yaml').AndReturn(module_config1)
+        absnames[0]).AndReturn(module_config1)
 
-    os.path.isdir('/appdir/other.yaml').AndReturn(False)
     module_config2 = ModuleConfigurationStub(module_name='other')
     application_configuration.ModuleConfiguration(
-        '/appdir/other.yaml').AndReturn(module_config2)
+        absnames[1]).AndReturn(module_config2)
 
     self.mox.ReplayAll()
     config = application_configuration.ApplicationConfiguration(
-        ['/appdir/app.yaml', '/appdir/other.yaml'])
+        absnames)
     self.mox.VerifyAll()
     self.assertEqual('myapp', config.app_id)
     self.assertSequenceEqual([module_config1, module_config2], config.modules)
 
   def test_yaml_files_with_different_app_ids(self):
-    os.path.isdir('/appdir/app.yaml').AndReturn(False)
+    absnames = self._make_file_hierarchy(
+        ['appdir/app.yaml', 'appdir/other.yaml'])
+
     module_config1 = ModuleConfigurationStub()
     application_configuration.ModuleConfiguration(
-        '/appdir/app.yaml').AndReturn(module_config1)
+        absnames[0]).AndReturn(module_config1)
 
-    os.path.isdir('/appdir/other.yaml').AndReturn(False)
     module_config2 = ModuleConfigurationStub(application='other_app',
                                              module_name='other')
     application_configuration.ModuleConfiguration(
-        '/appdir/other.yaml').AndReturn(module_config2)
+        absnames[1]).AndReturn(module_config2)
 
     self.mox.ReplayAll()
     self.assertRaises(errors.InvalidAppConfigError,
                       application_configuration.ApplicationConfiguration,
-                      ['/appdir/app.yaml', '/appdir/other.yaml'])
+                      absnames)
     self.mox.VerifyAll()
 
   def test_yaml_files_with_duplicate_module_names(self):
-    os.path.isdir('/appdir/app.yaml').AndReturn(False)
-    application_configuration.ModuleConfiguration(
-        '/appdir/app.yaml').AndReturn(ModuleConfigurationStub())
+    absnames = self._make_file_hierarchy(
+        ['appdir/app.yaml', 'appdir/other.yaml'])
 
-    os.path.isdir('/appdir/other.yaml').AndReturn(False)
     application_configuration.ModuleConfiguration(
-        '/appdir/other.yaml').AndReturn(ModuleConfigurationStub())
+        absnames[0]).AndReturn(ModuleConfigurationStub())
+
+    application_configuration.ModuleConfiguration(
+        absnames[1]).AndReturn(ModuleConfigurationStub())
 
     self.mox.ReplayAll()
     self.assertRaises(errors.InvalidAppConfigError,
                       application_configuration.ApplicationConfiguration,
-                      ['/appdir/app.yaml', '/appdir/other.yaml'])
+                      absnames)
     self.mox.VerifyAll()
 
   def test_directory(self):
-    os.path.isdir('/appdir').AndReturn(True)
-    os.path.exists(os.path.join('/appdir', 'app.yaml')).AndReturn(True)
-    os.path.exists(os.path.join('/appdir', 'backends.yaml')).AndReturn(False)
-    os.path.exists(os.path.join('/appdir', 'backends.yml')).AndReturn(False)
-    os.path.isdir(os.path.join('/appdir', 'app.yaml')).AndReturn(False)
+    absnames = self._make_file_hierarchy(['appdir/app.yaml'])
 
     module_config = ModuleConfigurationStub()
     application_configuration.ModuleConfiguration(
-        os.path.join('/appdir', 'app.yaml')).AndReturn(module_config)
-
-    self.mox.ReplayAll()
-    config = application_configuration.ApplicationConfiguration(['/appdir'])
-    self.mox.VerifyAll()
-    self.assertEqual('myapp', config.app_id)
-    self.assertSequenceEqual([module_config], config.modules)
-
-  def test_directory_app_yml_only(self):
-    os.path.isdir('/appdir').AndReturn(True)
-    os.path.exists(os.path.join('/appdir', 'app.yaml')).AndReturn(False)
-    os.path.exists(os.path.join('/appdir', 'app.yml')).AndReturn(True)
-    os.path.exists(os.path.join('/appdir', 'backends.yaml')).AndReturn(False)
-    os.path.exists(os.path.join('/appdir', 'backends.yml')).AndReturn(False)
-    os.path.isdir(os.path.join('/appdir', 'app.yml')).AndReturn(False)
-
-    module_config = ModuleConfigurationStub()
-    application_configuration.ModuleConfiguration(
-        os.path.join('/appdir', 'app.yml')).AndReturn(module_config)
-
-    self.mox.ReplayAll()
-    config = application_configuration.ApplicationConfiguration(['/appdir'])
-    self.mox.VerifyAll()
-    self.assertEqual('myapp', config.app_id)
-    self.assertSequenceEqual([module_config], config.modules)
-
-  def test_directory_no_app_yamls(self):
-    os.path.isdir('/appdir').AndReturn(True)
-    os.path.exists(os.path.join('/appdir', 'app.yaml')).AndReturn(False)
-    os.path.exists(os.path.join('/appdir', 'app.yml')).AndReturn(False)
-    self.mox.ReplayAll()
-    self.assertRaises(errors.AppConfigNotFoundError,
-                      application_configuration.ApplicationConfiguration,
-                      ['/appdir'])
-    self.mox.VerifyAll()
-
-  def test_app_yaml(self):
-    os.path.isdir('/appdir/app.yaml').AndReturn(False)
-    os.path.isdir('/appdir/app.yaml').AndReturn(False)
-
-    module_config = ModuleConfigurationStub()
-    application_configuration.ModuleConfiguration(
-        '/appdir/app.yaml').AndReturn(module_config)
+        absnames[0]).AndReturn(module_config)
 
     self.mox.ReplayAll()
     config = application_configuration.ApplicationConfiguration(
-        ['/appdir/app.yaml'])
+        [os.path.dirname(absnames[0])])
+    self.mox.VerifyAll()
+    self.assertEqual('myapp', config.app_id)
+    self.assertSequenceEqual([module_config], config.modules)
+
+  def test_directory_and_module(self):
+    absnames = self._make_file_hierarchy(
+        ['appdir/app.yaml', 'otherdir/mymodule.yaml'])
+
+    app_yaml_config = ModuleConfigurationStub()
+    application_configuration.ModuleConfiguration(
+        absnames[0]).AndReturn(app_yaml_config)
+    my_module_config = ModuleConfigurationStub(module_name='my_module')
+    application_configuration.ModuleConfiguration(
+        absnames[1]).AndReturn(my_module_config)
+    self.mox.ReplayAll()
+    config = application_configuration.ApplicationConfiguration(
+        [os.path.dirname(absnames[0]), absnames[1]])
+    self.mox.VerifyAll()
+    self.assertSequenceEqual(
+        [app_yaml_config, my_module_config], config.modules)
+
+  def test_directory_app_yml_only(self):
+    absnames = self._make_file_hierarchy(['appdir/app.yml'])
+
+    module_config = ModuleConfigurationStub()
+    application_configuration.ModuleConfiguration(
+        absnames[0]).AndReturn(module_config)
+
+    self.mox.ReplayAll()
+    config = application_configuration.ApplicationConfiguration(
+        [os.path.dirname(absnames[0])])
+    self.mox.VerifyAll()
+    self.assertEqual('myapp', config.app_id)
+    self.assertSequenceEqual([module_config], config.modules)
+
+  def test_directory_app_yaml_and_app_yml(self):
+    absnames = self._make_file_hierarchy(['appdir/app.yaml', 'appdir/app.yml'])
+    self.mox.ReplayAll()
+    self.assertRaises(errors.InvalidAppConfigError,
+                      application_configuration.ApplicationConfiguration,
+                      [os.path.dirname(absnames[0])])
+    self.mox.VerifyAll()
+
+  def test_directory_no_app_yamls(self):
+    absnames = self._make_file_hierarchy(['appdir/somethingelse.yaml'])
+
+    self.mox.ReplayAll()
+    self.assertRaises(errors.AppConfigNotFoundError,
+                      application_configuration.ApplicationConfiguration,
+                      [os.path.dirname(absnames[0])])
+    self.mox.VerifyAll()
+
+  def test_directory_no_app_yamls_or_web_inf(self):
+    absnames = self._make_file_hierarchy(['appdir/somethingelse.yaml'])
+
+    self.mox.ReplayAll()
+    with _java_temporarily_supported():
+      self.assertRaises(errors.AppConfigNotFoundError,
+                        application_configuration.ApplicationConfiguration,
+                        [os.path.dirname(absnames[0])])
+    self.mox.VerifyAll()
+
+  def test_app_yaml(self):
+    absnames = self._make_file_hierarchy(['appdir/app.yaml'])
+
+    module_config = ModuleConfigurationStub()
+    application_configuration.ModuleConfiguration(
+        absnames[0]).AndReturn(module_config)
+
+    self.mox.ReplayAll()
+    config = application_configuration.ApplicationConfiguration(absnames)
     self.mox.VerifyAll()
     self.assertEqual('myapp', config.app_id)
     self.assertSequenceEqual([module_config], config.modules)
 
   def test_directory_with_backends_yaml(self):
-    os.path.isdir('/appdir').AndReturn(True)
-    os.path.exists(os.path.join('/appdir', 'app.yaml')).AndReturn(True)
-    os.path.isdir(os.path.join('/appdir', 'app.yaml')).AndReturn(False)
-    os.path.exists(os.path.join('/appdir', 'backends.yaml')).AndReturn(True)
-    os.path.isdir(os.path.join('/appdir', 'backends.yaml')).AndReturn(False)
+    absnames = self._make_file_hierarchy(
+        ['appdir/app.yaml', 'appdir/backends.yaml'])
 
     module_config = ModuleConfigurationStub()
     application_configuration.ModuleConfiguration(
-        os.path.join('/appdir', 'app.yaml')).AndReturn(module_config)
+        absnames[0]).AndReturn(module_config)
     backend_config = ModuleConfigurationStub(module_name='backend')
     backends_config = self.mox.CreateMock(
         application_configuration.BackendsConfiguration)
     backends_config.get_backend_configurations().AndReturn([backend_config])
     application_configuration.BackendsConfiguration(
-        os.path.join('/appdir', 'app.yaml'),
-        os.path.join('/appdir', 'backends.yaml')).AndReturn(backends_config)
+        absnames[0], absnames[1]).AndReturn(backends_config)
 
     self.mox.ReplayAll()
-    config = application_configuration.ApplicationConfiguration(['/appdir'])
+    config = application_configuration.ApplicationConfiguration(
+        [os.path.dirname(absnames[0])])
     self.mox.VerifyAll()
     self.assertEqual('myapp', config.app_id)
     self.assertSequenceEqual([module_config, backend_config], config.modules)
 
   def test_yaml_files_with_backends_yaml(self):
-    os.path.isdir('/appdir/app.yaml').AndReturn(False)
+    absnames = self._make_file_hierarchy(
+        ['appdir/app.yaml', 'appdir/backends.yaml'])
+
     module_config = ModuleConfigurationStub()
     application_configuration.ModuleConfiguration(
-        '/appdir/app.yaml').AndReturn(module_config)
+        absnames[0]).AndReturn(module_config)
 
-    os.path.isdir('/appdir/backends.yaml').AndReturn(False)
     backend_config = ModuleConfigurationStub(module_name='backend')
     backends_config = self.mox.CreateMock(
         application_configuration.BackendsConfiguration)
     backends_config.get_backend_configurations().AndReturn([backend_config])
     application_configuration.BackendsConfiguration(
-        '/appdir/app.yaml',
-        '/appdir/backends.yaml').AndReturn(backends_config)
+        absnames[0], absnames[1]).AndReturn(backends_config)
 
     self.mox.ReplayAll()
-    config = application_configuration.ApplicationConfiguration(
-        ['/appdir/app.yaml', '/appdir/backends.yaml'])
+    config = application_configuration.ApplicationConfiguration(absnames)
     self.mox.VerifyAll()
     self.assertEqual('myapp', config.app_id)
     self.assertSequenceEqual([module_config, backend_config], config.modules)
 
   def test_yaml_files_with_backends_and_dispatch_yaml(self):
-    os.path.isdir('/appdir/app.yaml').AndReturn(False)
+    absnames = self._make_file_hierarchy(
+        ['appdir/app.yaml', 'appdir/backends.yaml', 'appdir/dispatch.yaml'])
+
     module_config = ModuleConfigurationStub(module_name='default')
     application_configuration.ModuleConfiguration(
-        '/appdir/app.yaml').AndReturn(module_config)
+        absnames[0]).AndReturn(module_config)
 
-    os.path.isdir('/appdir/backends.yaml').AndReturn(False)
     backend_config = ModuleConfigurationStub(module_name='backend')
     backends_config = self.mox.CreateMock(
         application_configuration.BackendsConfiguration)
     backends_config.get_backend_configurations().AndReturn([backend_config])
     application_configuration.BackendsConfiguration(
-        os.path.join('/appdir', 'app.yaml'),
-        os.path.join('/appdir', 'backends.yaml')).AndReturn(backends_config)
-    os.path.isdir('/appdir/dispatch.yaml').AndReturn(False)
+        absnames[0], absnames[1]).AndReturn(backends_config)
     dispatch_config = DispatchConfigurationStub(
         [(None, 'default'), (None, 'backend')])
     application_configuration.DispatchConfiguration(
-        '/appdir/dispatch.yaml').AndReturn(dispatch_config)
+        absnames[2]).AndReturn(dispatch_config)
 
     self.mox.ReplayAll()
-    config = application_configuration.ApplicationConfiguration(
-        ['/appdir/app.yaml', '/appdir/backends.yaml', '/appdir/dispatch.yaml'])
+    config = application_configuration.ApplicationConfiguration(absnames)
     self.mox.VerifyAll()
     self.assertEqual('myapp', config.app_id)
     self.assertSequenceEqual([module_config, backend_config], config.modules)
     self.assertEqual(dispatch_config, config.dispatch)
 
   def test_yaml_files_dispatch_yaml_and_no_default_module(self):
-    os.path.isdir('/appdir/app.yaml').AndReturn(False)
+    absnames = self._make_file_hierarchy(
+        ['appdir/app.yaml', 'appdir/dispatch.yaml'])
+
     module_config = ModuleConfigurationStub(module_name='not-default')
     application_configuration.ModuleConfiguration(
-        '/appdir/app.yaml').AndReturn(module_config)
+        absnames[0]).AndReturn(module_config)
 
-    os.path.isdir('/appdir/dispatch.yaml').AndReturn(False)
     dispatch_config = DispatchConfigurationStub([(None, 'default')])
     application_configuration.DispatchConfiguration(
-        '/appdir/dispatch.yaml').AndReturn(dispatch_config)
+        absnames[1]).AndReturn(dispatch_config)
 
     self.mox.ReplayAll()
     self.assertRaises(errors.InvalidAppConfigError,
                       application_configuration.ApplicationConfiguration,
-                      ['/appdir/app.yaml', '/appdir/dispatch.yaml'])
+                      absnames)
     self.mox.VerifyAll()
 
   def test_yaml_files_dispatch_yaml_and_missing_dispatch_target(self):
-    os.path.isdir('/appdir/app.yaml').AndReturn(False)
+    absnames = self._make_file_hierarchy(
+        ['appdir/app.yaml', 'appdir/dispatch.yaml'])
+
     module_config = ModuleConfigurationStub(module_name='default')
     application_configuration.ModuleConfiguration(
-        '/appdir/app.yaml').AndReturn(module_config)
+        absnames[0]).AndReturn(module_config)
 
-    os.path.isdir('/appdir/dispatch.yaml').AndReturn(False)
     dispatch_config = DispatchConfigurationStub(
         [(None, 'default'), (None, 'fake-module')])
     application_configuration.DispatchConfiguration(
-        '/appdir/dispatch.yaml').AndReturn(dispatch_config)
+        absnames[1]).AndReturn(dispatch_config)
 
     self.mox.ReplayAll()
     self.assertRaises(errors.InvalidAppConfigError,
                       application_configuration.ApplicationConfiguration,
-                      ['/appdir/app.yaml', '/appdir/dispatch.yaml'])
+                      absnames)
     self.mox.VerifyAll()
 
+  def test_directory_web_inf(self):
+    absnames = self._make_file_hierarchy(
+        ['appdir/WEB-INF/appengine-web.xml', 'appdir/WEB-INF/web.xml'])
+    appdir = os.path.dirname(os.path.dirname(absnames[0]))
+
+    module_config = ModuleConfigurationStub(module_name='default')
+    application_configuration.ModuleConfiguration(
+        absnames[0]).AndReturn(module_config)
+
+    self.mox.ReplayAll()
+    with _java_temporarily_supported():
+      config = application_configuration.ApplicationConfiguration([appdir])
+    self.mox.VerifyAll()
+
+    self.assertEqual('myapp', config.app_id)
+    self.assertSequenceEqual([module_config], config.modules)
+
+  def test_directory_web_inf_missing_appengine_xml(self):
+    absnames = self._make_file_hierarchy(['appdir/WEB-INF/web.xml'])
+    appdir = os.path.dirname(os.path.dirname(absnames[0]))
+
+    self.mox.ReplayAll()
+    with _java_temporarily_supported():
+      self.assertRaises(errors.AppConfigNotFoundError,
+                        application_configuration.ApplicationConfiguration,
+                        [appdir])
+    self.mox.VerifyAll()
+
+  def test_directory_web_inf_missing_web_xml(self):
+    absnames = self._make_file_hierarchy(['appdir/WEB-INF/appengine-web.xml'])
+    appdir = os.path.dirname(os.path.dirname(absnames[0]))
+
+    self.mox.ReplayAll()
+    with _java_temporarily_supported():
+      self.assertRaises(errors.AppConfigNotFoundError,
+                        application_configuration.ApplicationConfiguration,
+                        [appdir])
+    self.mox.VerifyAll()
+
+  def test_config_with_yaml_and_xml(self):
+    absnames = self._make_file_hierarchy(
+        ['module1/app.yaml', 'module1/dispatch.yaml',
+         'module2/WEB-INF/appengine-web.xml', 'module2/WEB-INF/web.xml'])
+    app_yaml = absnames[0]
+    dispatch_yaml = absnames[1]
+    appengine_web_xml = absnames[2]
+    module2 = os.path.dirname(os.path.dirname(appengine_web_xml))
+
+    module1_config = ModuleConfigurationStub(module_name='default')
+    application_configuration.ModuleConfiguration(
+        app_yaml).AndReturn(module1_config)
+    dispatch_config = DispatchConfigurationStub(
+        [(None, 'default'), (None, 'module2')])
+    application_configuration.DispatchConfiguration(
+        dispatch_yaml).AndReturn(dispatch_config)
+    module2_config = ModuleConfigurationStub(module_name='module2')
+    application_configuration.ModuleConfiguration(
+        appengine_web_xml).AndReturn(module2_config)
+
+    self.mox.ReplayAll()
+    with _java_temporarily_supported():
+      config = application_configuration.ApplicationConfiguration(
+          [app_yaml, dispatch_yaml, module2])
+    self.mox.VerifyAll()
+
+    self.assertEqual('myapp', config.app_id)
+    self.assertSequenceEqual(
+        [module1_config, module2_config], config.modules)
+    self.assertEqual(dispatch_config, config.dispatch)
+
+
 if __name__ == '__main__':
   unittest.main()
diff --git a/google/appengine/tools/devappserver2/devappserver2.py b/google/appengine/tools/devappserver2/devappserver2.py
index 86af330..619d3d8 100644
--- a/google/appengine/tools/devappserver2/devappserver2.py
+++ b/google/appengine/tools/devappserver2/devappserver2.py
@@ -306,7 +306,13 @@
 
   parser = argparse.ArgumentParser(
       formatter_class=argparse.ArgumentDefaultsHelpFormatter)
-  parser.add_argument('yaml_files', nargs='+')
+  arg_name = 'yaml_path'
+  arg_help = 'Path to a yaml file, or a directory containing yaml files'
+  if application_configuration.java_supported():
+    arg_name = 'yaml_or_war_path'
+    arg_help += ', or a directory containing WEB-INF/web.xml'
+  parser.add_argument(
+      'config_paths', metavar=arg_name, nargs='+', help=arg_help)
 
   common_group = parser.add_argument_group('Common')
   common_group.add_argument(
@@ -355,6 +361,7 @@
       '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"')
+  common_group.add_argument('--docker_daemon_url', help=argparse.SUPPRESS)
 
   # PHP
   php_group = parser.add_argument_group('PHP')
@@ -669,7 +676,7 @@
         _LOG_LEVEL_TO_PYTHON_CONSTANT[options.dev_appserver_log_level])
 
     configuration = application_configuration.ApplicationConfiguration(
-        options.yaml_files)
+        options.config_paths)
 
     if options.skip_sdk_update_check:
       logging.info('Skipping SDK update check.')
@@ -702,6 +709,7 @@
         self._create_php_config(options),
         self._create_python_config(options),
         self._create_cloud_sql_config(options),
+        self._create_vm_config(options),
         self._create_module_to_setting(options.max_module_instances,
                                        configuration, '--max_module_instances'),
         options.use_mtime_file_watcher,
@@ -847,6 +855,13 @@
     return cloud_sql_config
 
   @staticmethod
+  def _create_vm_config(options):
+    vm_config = runtime_config_pb2.VMConfig()
+    if options.docker_daemon_url:
+      vm_config.docker_daemon_url = options.docker_daemon_url
+    return vm_config
+
+  @staticmethod
   def _create_module_to_setting(setting, configuration, option):
     """Create a per-module dictionary configuration.
 
diff --git a/google/appengine/tools/devappserver2/dispatcher.py b/google/appengine/tools/devappserver2/dispatcher.py
index f1fa28c..a819f5f 100644
--- a/google/appengine/tools/devappserver2/dispatcher.py
+++ b/google/appengine/tools/devappserver2/dispatcher.py
@@ -75,11 +75,13 @@
                php_config,
                python_config,
                cloud_sql_config,
+               vm_config,
                module_to_max_instances,
                use_mtime_file_watcher,
                automatic_restart,
                allow_skipped_files,
                module_to_threadsafe_override):
+
     """Initializer for Dispatcher.
 
     Args:
@@ -101,6 +103,9 @@
       cloud_sql_config: A runtime_config_pb2.CloudSQL instance containing the
           required configuration for local Google Cloud SQL development. If None
           then Cloud SQL will not be available.
+      vm_config: A runtime_config_pb2.VMConfig instance containing
+          VM runtime-specific configuration. If vm_config does not have
+          docker_daemon_url specified all docker-related stuff is disabled.
       module_to_max_instances: A mapping between a module name and the maximum
           number of instances that can be created (this overrides the settings
           found in the configuration argument) e.g.
@@ -114,13 +119,14 @@
           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).
+          to override the module's YAML threadsafe configuration (so modules
+          not named continue to use their YAML configuration).
     """
     self._configuration = configuration
     self._php_config = php_config
     self._python_config = python_config
     self._cloud_sql_config = cloud_sql_config
+    self._vm_config = vm_config
     self._request_data = None
     self._api_host = None
     self._api_port = None
@@ -228,6 +234,7 @@
                    self._php_config,
                    self._python_config,
                    self._cloud_sql_config,
+                   self._vm_config,
                    self._port,
                    self._port_registry,
                    self._request_data,
@@ -237,6 +244,7 @@
                    self._automatic_restart,
                    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 d54fce2..7612ca5 100644
--- a/google/appengine/tools/devappserver2/dispatcher_test.py
+++ b/google/appengine/tools/devappserver2/dispatcher_test.py
@@ -100,6 +100,7 @@
         php_config=None,
         python_config=None,
         cloud_sql_config=None,
+        unused_vm_config=None,
         default_version_port=8080,
         port_registry=None,
         request_data=None,
@@ -141,6 +142,7 @@
         php_config=None,
         python_config=None,
         cloud_sql_config=None,
+        vm_config=None,
         default_version_port=8080,
         port_registry=None,
         request_data=None,
@@ -187,6 +189,7 @@
         php_config=None,
         python_config=None,
         cloud_sql_config=None,
+        vm_config=None,
         module_to_max_instances={},
         use_mtime_file_watcher=False,
         automatic_restart=True,
diff --git a/google/appengine/tools/devappserver2/environ_utils.py b/google/appengine/tools/devappserver2/environ_utils.py
new file mode 100644
index 0000000..192bd82
--- /dev/null
+++ b/google/appengine/tools/devappserver2/environ_utils.py
@@ -0,0 +1,46 @@
+#!/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.
+#
+"""Utils for working with environment variables in devappserver2."""
+
+from google.appengine.tools.devappserver2 import http_runtime_constants
+
+
+_ENVIRONS_TO_PROPAGATE_FULL_NAMES = set([
+    http_runtime_constants.APPENGINE_ENVIRON_PREFIX + x for x in
+    http_runtime_constants.ENVIRONS_TO_PROPAGATE])
+
+
+def propagate_environs(src, dst):
+  """Propagates environment variables to the request handlers.
+
+  Propagation rules:
+      Environs that do not start with HTTP_ do not propagate.
+      Environs formatted like APPENGINE_HEADER_PREFIX + name, where name is one
+          of ENVIRONS_TO_PROPAGATE set have APPENGINE_HEADER_PREFIX cut out.
+      Internal Dev AppServer environs starting with APPENGINE_DEV_ENVIRON_PREFIX
+          do not propagate.
+
+  Args:
+      src: source environ dict.
+      dst: destination environ dict.
+  """
+  for key, value in src.iteritems():
+    if key in _ENVIRONS_TO_PROPAGATE_FULL_NAMES:
+      dst[key[len(http_runtime_constants.APPENGINE_ENVIRON_PREFIX):]] = value
+    elif (key.startswith('HTTP_') and not
+          key.startswith(http_runtime_constants.APPENGINE_DEV_ENVIRON_PREFIX)):
+      dst[key] = value
diff --git a/google/appengine/tools/devappserver2/environ_utils_test.py b/google/appengine/tools/devappserver2/environ_utils_test.py
new file mode 100644
index 0000000..97390b3
--- /dev/null
+++ b/google/appengine/tools/devappserver2/environ_utils_test.py
@@ -0,0 +1,39 @@
+#!/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.
+#
+import unittest
+
+from google.appengine.tools.devappserver2 import environ_utils
+
+
+class EnvironUtilsTest(unittest.TestCase):
+
+  def test_propagate_environs(self):
+    src = {
+        'LALA': 'will not propagate',
+        'HTTP_LALA': 'will propagate',
+        'HTTP_X_APPENGINE_USER_ID': '12345',
+        'HTTP_X_APPENGINE_DEV_MYENV': 'will not propagate either'
+    }
+
+    dst = {}
+    environ_utils.propagate_environs(src, dst)
+    self.assertEqual(dst, {'HTTP_LALA': 'will propagate',
+                           'USER_ID': '12345'})
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/google/appengine/tools/devappserver2/http_proxy.py b/google/appengine/tools/devappserver2/http_proxy.py
new file mode 100644
index 0000000..8e07ae8
--- /dev/null
+++ b/google/appengine/tools/devappserver2/http_proxy.py
@@ -0,0 +1,214 @@
+#!/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.
+#
+"""Forwards HTTP requests to an application instance on a given host and port.
+
+An instance (also referred to as a runtime process) handles dynamic content
+only. Static files are handled separately.
+"""
+
+import contextlib
+import httplib
+import logging
+import socket
+import urllib
+import wsgiref.headers
+
+from google.appengine.tools.devappserver2 import http_runtime_constants
+from google.appengine.tools.devappserver2 import instance
+from google.appengine.tools.devappserver2 import login
+from google.appengine.tools.devappserver2 import util
+
+
+class HttpProxy:
+  """Forwards HTTP requests to an application instance."""
+  def __init__(self, host, port, instance_died_unexpectedly,
+               instance_logs_getter, error_handler_file, prior_error=None):
+    """Initializer for HttpProxy.
+
+    Args:
+      host: A hostname or an IP address of where application instance is
+          running.
+      port: Port that application instance is listening on.
+      instance_died_unexpectedly: Function returning True if instance has
+          unexpectedly died.
+      instance_logs_getter: Function returning logs from the instance.
+      error_handler_file: Application specific error handler for default error
+          code if specified (only default error code is supported by
+          Dev AppServer).
+      prior_error: Errors occurred before (for example during creation of an
+          instance). In case prior_error is not None handle will always return
+          corresponding error message without even trying to connect to the
+          instance.
+    """
+    self._host = host
+    self._port = port
+    self._instance_died_unexpectedly = instance_died_unexpectedly
+    self._instance_logs_getter = instance_logs_getter
+    self._error_handler_file = error_handler_file
+    self._prior_error = prior_error
+
+  def _respond_with_error(self, message, start_response):
+    instance_logs = self._instance_logs_getter()
+    if instance_logs:
+      message += '\n\n' + instance_logs
+    # TODO: change 'text/plain' to 'text/plain; charset=utf-8'
+    # throughout devappserver2.
+    start_response('500 Internal Server Error',
+                   [('Content-Type', 'text/plain'),
+                    ('Content-Length', str(len(message)))])
+    return message
+
+  def handle(self, environ, start_response, url_map, match, request_id,
+             request_type):
+    """Serves this request by forwarding it to the runtime process.
+
+    Args:
+      environ: An environ dict for the request as defined in PEP-333.
+      start_response: A function with semantics defined in PEP-333.
+      url_map: An appinfo.URLMap instance containing the configuration for the
+          handler matching this request.
+      match: A re.MatchObject containing the result of the matched URL pattern.
+      request_id: A unique string id associated with the request.
+      request_type: The type of the request. See instance.*_REQUEST module
+          constants.
+
+    Yields:
+      A sequence of strings containing the body of the HTTP response.
+    """
+
+    if self._prior_error:
+      logging.error(self._prior_error)
+      yield self._respond_with_error(self._prior_error, start_response)
+      return
+
+    environ[http_runtime_constants.SCRIPT_HEADER] = match.expand(url_map.script)
+    if request_type == instance.BACKGROUND_REQUEST:
+      environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'background'
+    elif request_type == instance.SHUTDOWN_REQUEST:
+      environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'shutdown'
+    elif request_type == instance.INTERACTIVE_REQUEST:
+      environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'interactive'
+
+    for name in http_runtime_constants.ENVIRONS_TO_PROPAGATE:
+      if http_runtime_constants.APPENGINE_ENVIRON_PREFIX + name not in environ:
+        value = environ.get(name, None)
+        if value is not None:
+          environ[
+              http_runtime_constants.APPENGINE_ENVIRON_PREFIX + name] = value
+    headers = util.get_headers_from_environ(environ)
+    if environ.get('QUERY_STRING'):
+      url = '%s?%s' % (urllib.quote(environ['PATH_INFO']),
+                       environ['QUERY_STRING'])
+    else:
+      url = urllib.quote(environ['PATH_INFO'])
+    if 'CONTENT_LENGTH' in environ:
+      headers['CONTENT-LENGTH'] = environ['CONTENT_LENGTH']
+      data = environ['wsgi.input'].read(int(environ['CONTENT_LENGTH']))
+    else:
+      data = ''
+
+    cookies = environ.get('HTTP_COOKIE')
+    user_email, admin, user_id = login.get_user_info(cookies)
+    if user_email:
+      nickname, organization = user_email.split('@', 1)
+    else:
+      nickname = ''
+      organization = ''
+    headers[http_runtime_constants.REQUEST_ID_HEADER] = request_id
+    headers[http_runtime_constants.APPENGINE_HEADER_PREFIX + 'User-Id'] = (
+        user_id)
+    headers[http_runtime_constants.APPENGINE_HEADER_PREFIX + 'User-Email'] = (
+        user_email)
+    headers[
+        http_runtime_constants.APPENGINE_HEADER_PREFIX + 'User-Is-Admin'] = (
+            str(int(admin)))
+    headers[
+        http_runtime_constants.APPENGINE_HEADER_PREFIX + 'User-Nickname'] = (
+            nickname)
+    headers[http_runtime_constants.APPENGINE_HEADER_PREFIX +
+            'User-Organization'] = organization
+    headers['X-AppEngine-Country'] = 'ZZ'
+    connection = httplib.HTTPConnection(self._host, self._port)
+    with contextlib.closing(connection):
+      try:
+        connection.connect()
+        connection.request(environ.get('REQUEST_METHOD', 'GET'),
+                           url,
+                           data,
+                           dict(headers.items()))
+
+        try:
+          response = connection.getresponse()
+        except httplib.HTTPException as e:
+          # The runtime process has written a bad HTTP response. For example,
+          # a Go runtime process may have crashed in app-specific code.
+          yield self._respond_with_error(
+              'the runtime process gave a bad HTTP response: %s' % e,
+              start_response)
+          return
+
+        # Ensures that we avoid merging repeat headers into a single header,
+        # allowing use of multiple Set-Cookie headers.
+        headers = []
+        for name in response.msg:
+          for value in response.msg.getheaders(name):
+            headers.append((name, value))
+
+        response_headers = wsgiref.headers.Headers(headers)
+
+        if self._error_handler_file and (
+            http_runtime_constants.ERROR_CODE_HEADER in response_headers):
+          try:
+            with open(self._error_handler_file) as f:
+              content = f.read()
+          except IOError:
+            content = 'Failed to load error handler'
+            logging.exception('failed to load error file: %s',
+                              self._error_handler_file)
+          start_response('500 Internal Server Error',
+                         [('Content-Type', 'text/html'),
+                          ('Content-Length', str(len(content)))])
+          yield content
+          return
+        del response_headers[http_runtime_constants.ERROR_CODE_HEADER]
+        start_response('%s %s' % (response.status, response.reason),
+                       response_headers.items())
+
+        # Yield the response body in small blocks.
+        while True:
+          try:
+            block = response.read(512)
+            if not block:
+              break
+            yield block
+          except httplib.HTTPException:
+            # The runtime process has encountered a problem, but has not
+            # necessarily crashed. For example, a Go runtime process' HTTP
+            # handler may have panicked in app-specific code (which the http
+            # package will recover from, so the process as a whole doesn't
+            # crash). At this point, we have already proxied onwards the HTTP
+            # header, so we cannot retroactively serve a 500 Internal Server
+            # Error. We silently break here; the runtime process has presumably
+            # already written to stderr (via the Tee).
+            break
+      except Exception:
+        if self._instance_died_unexpectedly():
+          yield self._respond_with_error(
+              'the runtime process for the instance running on port %d has '
+              'unexpectedly quit' % self._port, start_response)
+        else:
+          raise
diff --git a/google/appengine/tools/devappserver2/http_proxy_test.py b/google/appengine/tools/devappserver2/http_proxy_test.py
new file mode 100644
index 0000000..1bf6fc7
--- /dev/null
+++ b/google/appengine/tools/devappserver2/http_proxy_test.py
@@ -0,0 +1,548 @@
+#!/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.
+#
+"""Tests for google.appengine.tools.devappserver2.http_proxy."""
+
+import cStringIO
+import httplib
+import os
+import re
+import shutil
+import socket
+import tempfile
+import unittest
+
+import google
+
+import mox
+
+from google.appengine.api import appinfo
+from google.appengine.tools.devappserver2 import http_proxy
+from google.appengine.tools.devappserver2 import http_runtime_constants
+from google.appengine.tools.devappserver2 import instance
+from google.appengine.tools.devappserver2 import login
+from google.appengine.tools.devappserver2 import wsgi_test_utils
+
+
+class MockMessage(object):
+  def __init__(self, headers):
+    self.headers = headers
+
+  def __iter__(self):
+    return iter(set(name for name, _ in self.headers))
+
+  def getheaders(self, name):
+    return [value for header_name, value in self.headers if header_name == name]
+
+
+class FakeHttpResponse(object):
+  def __init__(self, status, reason, headers, body):
+    self.body = body
+    self.has_read = False
+    self.partial_read_error = None
+    self.status = status
+    self.reason = reason
+    self.headers = headers
+    self.msg = MockMessage(headers)
+
+  def read(self, amt=None):
+    if not self.has_read:
+      self.has_read = True
+      return self.body
+    elif self.partial_read_error:
+      raise self.partial_read_error
+    else:
+      return ''
+
+  def getheaders(self):
+    return self.headers
+
+
+def get_instance_logs():
+  return ''
+
+
+class HttpProxyTest(wsgi_test_utils.WSGITestCase):
+  def setUp(self):
+    self.mox = mox.Mox()
+    self.tmpdir = tempfile.mkdtemp()
+
+    self.proxy = http_proxy.HttpProxy(
+        host='localhost', port=23456,
+        instance_died_unexpectedly=lambda: False,
+        instance_logs_getter=get_instance_logs,
+        error_handler_file=None)
+
+    self.mox.StubOutWithMock(httplib.HTTPConnection, 'connect')
+    self.mox.StubOutWithMock(httplib.HTTPConnection, 'request')
+    self.mox.StubOutWithMock(httplib.HTTPConnection, 'getresponse')
+    self.mox.StubOutWithMock(httplib.HTTPConnection, 'close')
+    self.mox.StubOutWithMock(login, 'get_user_info')
+    self.url_map = appinfo.URLMap(url=r'/(get|post).*',
+                                  script=r'\1.py')
+
+  def tearDown(self):
+    shutil.rmtree(self.tmpdir)
+    self.mox.UnsetStubs()
+
+  def test_handle_get(self):
+    response = FakeHttpResponse(200,
+                                'OK',
+                                [('Foo', 'a'), ('Foo', 'b'), ('Var', 'c')],
+                                'response')
+    login.get_user_info(None).AndReturn(('', False, ''))
+    httplib.HTTPConnection.connect()
+    httplib.HTTPConnection.request(
+        'GET', '/get%20request?key=value', '',
+        {'HEADER': 'value',
+         http_runtime_constants.REQUEST_ID_HEADER: 'request id',
+         'X-AppEngine-Country': 'ZZ',
+         'X-Appengine-User-Email': '',
+         'X-Appengine-User-Id': '',
+         'X-Appengine-User-Is-Admin': '0',
+         'X-Appengine-User-Nickname': '',
+         'X-Appengine-User-Organization': '',
+         'X-APPENGINE-DEV-SCRIPT': 'get.py',
+         'X-APPENGINE-SERVER-NAME': 'localhost',
+         'X-APPENGINE-SERVER-PORT': '8080',
+         'X-APPENGINE-SERVER-PROTOCOL': 'HTTP/1.1',
+        })
+    httplib.HTTPConnection.getresponse().AndReturn(response)
+    httplib.HTTPConnection.close()
+    environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get request',
+               'QUERY_STRING': 'key=value',
+               'HTTP_X_APPENGINE_USER_ID': '123',
+               'SERVER_NAME': 'localhost',
+               'SERVER_PORT': '8080',
+               'SERVER_PROTOCOL': 'HTTP/1.1',
+              }
+    self.mox.ReplayAll()
+    expected_headers = [('Foo', 'a'), ('Foo', 'b'), ('Var', 'c')]
+    self.assertResponse('200 OK', expected_headers, 'response',
+                        self.proxy.handle, environ,
+                        url_map=self.url_map,
+                        match=re.match(self.url_map.url, '/get%20request'),
+                        request_id='request id',
+                        request_type=instance.NORMAL_REQUEST)
+    self.mox.VerifyAll()
+
+  def test_handle_post(self):
+    response = FakeHttpResponse(200,
+                                'OK',
+                                [('Foo', 'a'), ('Foo', 'b'), ('Var', 'c')],
+                                'response')
+    login.get_user_info('cookie').AndReturn(('user@example.com', True, '12345'))
+    httplib.HTTPConnection.connect()
+    httplib.HTTPConnection.request(
+        'POST', '/post', 'post data',
+        {'HEADER': 'value',
+         'COOKIE': 'cookie',
+         'CONTENT-TYPE': 'text/plain',
+         'CONTENT-LENGTH': '9',
+         http_runtime_constants.REQUEST_ID_HEADER: 'request id',
+         'X-AppEngine-Country': 'ZZ',
+         'X-Appengine-User-Email': 'user@example.com',
+         'X-Appengine-User-Id': '12345',
+         'X-Appengine-User-Is-Admin': '1',
+         'X-Appengine-User-Nickname': 'user',
+         'X-Appengine-User-Organization': 'example.com',
+         'X-APPENGINE-DEV-SCRIPT': 'post.py',
+         'X-APPENGINE-SERVER-NAME': 'localhost',
+         'X-APPENGINE-SERVER-PORT': '8080',
+         'X-APPENGINE-SERVER-PROTOCOL': 'HTTP/1.1',
+        })
+    httplib.HTTPConnection.getresponse().AndReturn(response)
+    httplib.HTTPConnection.close()
+    environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/post',
+               'wsgi.input': cStringIO.StringIO('post data'),
+               'CONTENT_LENGTH': '9',
+               'CONTENT_TYPE': 'text/plain',
+               'REQUEST_METHOD': 'POST',
+               'HTTP_COOKIE': 'cookie',
+               'SERVER_NAME': 'localhost',
+               'SERVER_PORT': '8080',
+               'SERVER_PROTOCOL': 'HTTP/1.1',
+              }
+    self.mox.ReplayAll()
+    expected_headers = [('Foo', 'a'), ('Foo', 'b'), ('Var', 'c')]
+    self.assertResponse('200 OK', expected_headers, 'response',
+                        self.proxy.handle, environ,
+                        url_map=self.url_map,
+                        match=re.match(self.url_map.url, '/post'),
+                        request_id='request id',
+                        request_type=instance.NORMAL_REQUEST)
+    self.mox.VerifyAll()
+
+  def test_handle_with_error(self):
+    error_handler_file = os.path.join(self.tmpdir, 'error.html')
+    with open(error_handler_file, 'w') as f:
+      f.write('error')
+
+    self.proxy = http_proxy.HttpProxy(
+        host='localhost', port=23456,
+        instance_died_unexpectedly=lambda: False,
+        instance_logs_getter=get_instance_logs,
+        error_handler_file=error_handler_file)
+
+    response = FakeHttpResponse(
+        500, 'Internal Server Error',
+        [(http_runtime_constants.ERROR_CODE_HEADER, '1')], '')
+    login.get_user_info(None).AndReturn(('', False, ''))
+    httplib.HTTPConnection.connect()
+    httplib.HTTPConnection.request(
+        'GET', '/get%20error', '',
+        {'HEADER': 'value',
+         http_runtime_constants.REQUEST_ID_HEADER: 'request id',
+         'X-AppEngine-Country': 'ZZ',
+         'X-Appengine-User-Email': '',
+         'X-Appengine-User-Id': '',
+         'X-Appengine-User-Is-Admin': '0',
+         'X-Appengine-User-Nickname': '',
+         'X-Appengine-User-Organization': '',
+         'X-APPENGINE-DEV-SCRIPT': 'get.py',
+         'X-APPENGINE-SERVER-NAME': 'localhost',
+         'X-APPENGINE-SERVER-PORT': '8080',
+         'X-APPENGINE-SERVER-PROTOCOL': 'HTTP/1.1',
+        })
+    httplib.HTTPConnection.getresponse().AndReturn(response)
+    httplib.HTTPConnection.close()
+    environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get error',
+               'QUERY_STRING': '',
+               'HTTP_X_APPENGINE_USER_ID': '123',
+               'SERVER_NAME': 'localhost',
+               'SERVER_PORT': '8080',
+               'SERVER_PROTOCOL': 'HTTP/1.1',
+              }
+    self.mox.ReplayAll()
+    expected_headers = {
+        'Content-Type': 'text/html',
+        'Content-Length': '5',
+    }
+    self.assertResponse('500 Internal Server Error', expected_headers, 'error',
+                        self.proxy.handle, environ,
+                        url_map=self.url_map,
+                        match=re.match(self.url_map.url, '/get%20error'),
+                        request_id='request id',
+                        request_type=instance.NORMAL_REQUEST)
+    self.mox.VerifyAll()
+
+  def test_handle_with_error_no_error_handler(self):
+    self.proxy = http_proxy.HttpProxy(
+        host='localhost', port=23456,
+        instance_died_unexpectedly=lambda: False,
+        instance_logs_getter=get_instance_logs,
+        error_handler_file=None)
+    response = FakeHttpResponse(
+        500, 'Internal Server Error',
+        [(http_runtime_constants.ERROR_CODE_HEADER, '1')], '')
+    login.get_user_info(None).AndReturn(('', False, ''))
+    httplib.HTTPConnection.connect()
+    httplib.HTTPConnection.request(
+        'GET', '/get%20error', '',
+        {'HEADER': 'value',
+         http_runtime_constants.REQUEST_ID_HEADER: 'request id',
+         'X-AppEngine-Country': 'ZZ',
+         'X-Appengine-User-Email': '',
+         'X-Appengine-User-Id': '',
+         'X-Appengine-User-Is-Admin': '0',
+         'X-Appengine-User-Nickname': '',
+         'X-Appengine-User-Organization': '',
+         'X-APPENGINE-DEV-SCRIPT': 'get.py',
+         'X-APPENGINE-SERVER-NAME': 'localhost',
+         'X-APPENGINE-SERVER-PORT': '8080',
+         'X-APPENGINE-SERVER-PROTOCOL': 'HTTP/1.1',
+        })
+    httplib.HTTPConnection.getresponse().AndReturn(response)
+    httplib.HTTPConnection.close()
+    environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get error',
+               'QUERY_STRING': '',
+               'HTTP_X_APPENGINE_USER_ID': '123',
+               'SERVER_NAME': 'localhost',
+               'SERVER_PORT': '8080',
+               'SERVER_PROTOCOL': 'HTTP/1.1',
+              }
+    self.mox.ReplayAll()
+    self.assertResponse('500 Internal Server Error', {}, '',
+                        self.proxy.handle, environ,
+                        url_map=self.url_map,
+                        match=re.match(self.url_map.url, '/get%20error'),
+                        request_id='request id',
+                        request_type=instance.NORMAL_REQUEST)
+    self.mox.VerifyAll()
+
+  def test_handle_with_error_missing_error_handler(self):
+    error_handler_file = os.path.join(self.tmpdir, 'error.html')
+
+    self.proxy = http_proxy.HttpProxy(
+        host='localhost', port=23456,
+        instance_died_unexpectedly=lambda: False,
+        instance_logs_getter=get_instance_logs,
+        error_handler_file=error_handler_file)
+
+    response = FakeHttpResponse(
+        500, 'Internal Server Error',
+        [(http_runtime_constants.ERROR_CODE_HEADER, '1')], '')
+    login.get_user_info(None).AndReturn(('', False, ''))
+    httplib.HTTPConnection.connect()
+    httplib.HTTPConnection.request(
+        'GET', '/get%20error', '',
+        {'HEADER': 'value',
+         http_runtime_constants.REQUEST_ID_HEADER: 'request id',
+         'X-AppEngine-Country': 'ZZ',
+         'X-Appengine-User-Email': '',
+         'X-Appengine-User-Id': '',
+         'X-Appengine-User-Is-Admin': '0',
+         'X-Appengine-User-Nickname': '',
+         'X-Appengine-User-Organization': '',
+         'X-APPENGINE-DEV-SCRIPT': 'get.py',
+         'X-APPENGINE-SERVER-NAME': 'localhost',
+         'X-APPENGINE-SERVER-PORT': '8080',
+         'X-APPENGINE-SERVER-PROTOCOL': 'HTTP/1.1',
+        })
+    httplib.HTTPConnection.getresponse().AndReturn(response)
+    httplib.HTTPConnection.close()
+    environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get error',
+               'QUERY_STRING': '',
+               'HTTP_X_APPENGINE_USER_ID': '123',
+               'SERVER_NAME': 'localhost',
+               'SERVER_PORT': '8080',
+               'SERVER_PROTOCOL': 'HTTP/1.1',
+              }
+    self.mox.ReplayAll()
+    expected_headers = {
+        'Content-Type': 'text/html',
+        'Content-Length': '28',
+    }
+    self.assertResponse('500 Internal Server Error', expected_headers,
+                        'Failed to load error handler', self.proxy.handle,
+                        environ, url_map=self.url_map,
+                        match=re.match(self.url_map.url, '/get%20error'),
+                        request_id='request id',
+                        request_type=instance.NORMAL_REQUEST)
+    self.mox.VerifyAll()
+
+  def test_http_response_early_failure(self):
+    header = ('the runtime process gave a bad HTTP response: '
+              'IncompleteRead(0 bytes read)\n\n')
+    def dave_message():
+      return "I'm sorry, Dave. I'm afraid I can't do that.\n"
+
+    self.proxy = http_proxy.HttpProxy(
+        host='localhost', port=23456,
+        instance_died_unexpectedly=lambda: False,
+        instance_logs_getter=dave_message,
+        error_handler_file=None)
+
+    login.get_user_info(None).AndReturn(('', False, ''))
+    httplib.HTTPConnection.connect()
+    httplib.HTTPConnection.request(
+        'GET', '/get%20request?key=value', '',
+        {'HEADER': 'value',
+         http_runtime_constants.REQUEST_ID_HEADER: 'request id',
+         'X-AppEngine-Country': 'ZZ',
+         'X-Appengine-User-Email': '',
+         'X-Appengine-User-Id': '',
+         'X-Appengine-User-Is-Admin': '0',
+         'X-Appengine-User-Nickname': '',
+         'X-Appengine-User-Organization': '',
+         'X-APPENGINE-DEV-SCRIPT': 'get.py',
+         'X-APPENGINE-SERVER-NAME': 'localhost',
+         'X-APPENGINE-SERVER-PORT': '8080',
+         'X-APPENGINE-SERVER-PROTOCOL': 'HTTP/1.1',
+        })
+    httplib.HTTPConnection.getresponse().AndRaise(httplib.IncompleteRead(''))
+    httplib.HTTPConnection.close()
+    environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get request',
+               'QUERY_STRING': 'key=value',
+               'HTTP_X_APPENGINE_USER_ID': '123',
+               'SERVER_NAME': 'localhost',
+               'SERVER_PORT': '8080',
+               'SERVER_PROTOCOL': 'HTTP/1.1',
+              }
+    self.mox.ReplayAll()
+    expected_headers = {
+        'Content-Type': 'text/plain',
+        'Content-Length': '%d' % (len(header) + len(dave_message()))
+    }
+
+    self.assertResponse('500 Internal Server Error', expected_headers,
+                        header + dave_message(),
+                        self.proxy.handle, environ,
+                        url_map=self.url_map,
+                        match=re.match(self.url_map.url, '/get%20request'),
+                        request_id='request id',
+                        request_type=instance.NORMAL_REQUEST)
+    self.mox.VerifyAll()
+
+  def test_http_response_late_failure(self):
+    line0 = "I know I've made some very poor decisions recently...\n"
+    def dave_message():
+      return "I'm afraid. I'm afraid, Dave.\n"
+
+    self.proxy = http_proxy.HttpProxy(
+        host='localhost', port=23456,
+        instance_died_unexpectedly=lambda: False,
+        instance_logs_getter=dave_message,
+        error_handler_file=None)
+
+    response = FakeHttpResponse(200, 'OK', [], line0)
+    response.partial_read_error = httplib.IncompleteRead('')
+    login.get_user_info(None).AndReturn(('', False, ''))
+    httplib.HTTPConnection.connect()
+    httplib.HTTPConnection.request(
+        'GET', '/get%20request?key=value', '',
+        {'HEADER': 'value',
+         http_runtime_constants.REQUEST_ID_HEADER: 'request id',
+         'X-AppEngine-Country': 'ZZ',
+         'X-Appengine-User-Email': '',
+         'X-Appengine-User-Id': '',
+         'X-Appengine-User-Is-Admin': '0',
+         'X-Appengine-User-Nickname': '',
+         'X-Appengine-User-Organization': '',
+         'X-APPENGINE-DEV-SCRIPT': 'get.py',
+         'X-APPENGINE-SERVER-NAME': 'localhost',
+         'X-APPENGINE-SERVER-PORT': '8080',
+         'X-APPENGINE-SERVER-PROTOCOL': 'HTTP/1.1',
+        })
+    httplib.HTTPConnection.getresponse().AndReturn(response)
+    httplib.HTTPConnection.close()
+    environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get request',
+               'QUERY_STRING': 'key=value',
+               'HTTP_X_APPENGINE_USER_ID': '123',
+               'SERVER_NAME': 'localhost',
+               'SERVER_PORT': '8080',
+               'SERVER_PROTOCOL': 'HTTP/1.1',
+              }
+    self.mox.ReplayAll()
+    self.assertResponse('200 OK', {},
+                        line0,
+                        self.proxy.handle, environ,
+                        url_map=self.url_map,
+                        match=re.match(self.url_map.url, '/get%20request'),
+                        request_id='request id',
+                        request_type=instance.NORMAL_REQUEST)
+    self.mox.VerifyAll()
+
+  def test_connection_error(self):
+    login.get_user_info(None).AndReturn(('', False, ''))
+    httplib.HTTPConnection.connect().AndRaise(socket.error())
+    httplib.HTTPConnection.close()
+
+    self.mox.ReplayAll()
+    self.assertRaises(socket.error,
+                      self.proxy.handle(
+                          {'PATH_INFO': '/'},
+                          start_response=None,  # Not used.
+                          url_map=self.url_map,
+                          match=re.match(self.url_map.url, '/get%20error'),
+                          request_id='request id',
+                          request_type=instance.NORMAL_REQUEST).next)
+    self.mox.VerifyAll()
+
+  def test_connection_error_process_quit(self):
+    self.proxy = http_proxy.HttpProxy(
+        host='localhost', port=123,
+        instance_died_unexpectedly=lambda: True,
+        instance_logs_getter=get_instance_logs,
+        error_handler_file=None)
+    login.get_user_info(None).AndReturn(('', False, ''))
+    httplib.HTTPConnection.connect().AndRaise(socket.error())
+    httplib.HTTPConnection.close()
+
+    self.mox.ReplayAll()
+    expected_headers = {
+        'Content-Type': 'text/plain',
+        'Content-Length': '78',
+    }
+    expected_content = ('the runtime process for the instance running on port '
+                        '123 has unexpectedly quit')
+    self.assertResponse('500 Internal Server Error',
+                        expected_headers,
+                        expected_content,
+                        self.proxy.handle,
+                        {'PATH_INFO': '/'},
+                        url_map=self.url_map,
+                        match=re.match(self.url_map.url, '/get%20error'),
+                        request_id='request id',
+                        request_type=instance.NORMAL_REQUEST)
+    self.mox.VerifyAll()
+
+  def test_handle_background_thread(self):
+    response = FakeHttpResponse(200, 'OK', [('Foo', 'Bar')], 'response')
+    login.get_user_info(None).AndReturn(('', False, ''))
+    httplib.HTTPConnection.connect()
+    httplib.HTTPConnection.request(
+        'GET', '/get%20request?key=value', '',
+        {'HEADER': 'value',
+         http_runtime_constants.REQUEST_ID_HEADER: 'request id',
+         'X-AppEngine-Country': 'ZZ',
+         'X-Appengine-User-Email': '',
+         'X-Appengine-User-Id': '',
+         'X-Appengine-User-Is-Admin': '0',
+         'X-Appengine-User-Nickname': '',
+         'X-Appengine-User-Organization': '',
+         'X-APPENGINE-DEV-SCRIPT': 'get.py',
+         'X-APPENGINE-DEV-REQUEST-TYPE': 'background',
+         'X-APPENGINE-SERVER-NAME': 'localhost',
+         'X-APPENGINE-SERVER-PORT': '8080',
+         'X-APPENGINE-SERVER-PROTOCOL': 'HTTP/1.1',
+        })
+    httplib.HTTPConnection.getresponse().AndReturn(response)
+    httplib.HTTPConnection.close()
+    environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get request',
+               'QUERY_STRING': 'key=value',
+               'HTTP_X_APPENGINE_USER_ID': '123',
+               'SERVER_NAME': 'localhost',
+               'SERVER_PORT': '8080',
+               'SERVER_PROTOCOL': 'HTTP/1.1',
+              }
+    self.mox.ReplayAll()
+    expected_headers = {
+        'Foo': 'Bar',
+    }
+    self.assertResponse('200 OK', expected_headers, 'response',
+                        self.proxy.handle, environ,
+                        url_map=self.url_map,
+                        match=re.match(self.url_map.url, '/get%20request'),
+                        request_id='request id',
+                        request_type=instance.BACKGROUND_REQUEST)
+    self.mox.VerifyAll()
+
+  def test_prior_error(self):
+    error = 'Oh no! Something is broken again!'
+    self.proxy = http_proxy.HttpProxy(
+        host=None, port=None,
+        instance_died_unexpectedly=None,
+        instance_logs_getter=get_instance_logs,
+        error_handler_file=None,
+        prior_error=error)
+
+    expected_headers = {
+        'Content-Type': 'text/plain',
+        'Content-Length': str(len(error)),
+    }
+    self.assertResponse('500 Internal Server Error', expected_headers,
+                        error,
+                        self.proxy.handle, {},
+                        url_map=self.url_map,
+                        match=re.match(self.url_map.url, '/get%20request'),
+                        request_id='request id',
+                        request_type=instance.NORMAL_REQUEST)
+    self.mox.VerifyAll()
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/google/appengine/tools/devappserver2/http_runtime.py b/google/appengine/tools/devappserver2/http_runtime.py
index fc27406..ee5957b 100644
--- a/google/appengine/tools/devappserver2/http_runtime.py
+++ b/google/appengine/tools/devappserver2/http_runtime.py
@@ -35,24 +35,18 @@
 
 
 import base64
-import contextlib
-import httplib
 import logging
 import os
-import socket
 import subprocess
 import sys
 import time
 import threading
-import urllib
-import wsgiref.headers
 
-from google.appengine.tools.devappserver2 import http_runtime_constants
+from google.appengine.tools.devappserver2 import application_configuration
+from google.appengine.tools.devappserver2 import http_proxy
 from google.appengine.tools.devappserver2 import instance
-from google.appengine.tools.devappserver2 import login
 from google.appengine.tools.devappserver2 import safe_subprocess
 from google.appengine.tools.devappserver2 import tee
-from google.appengine.tools.devappserver2 import util
 
 START_PROCESS = -1
 START_PROCESS_FILE = -2
@@ -140,11 +134,8 @@
       ValueError: An unknown value for start_process_flavor was used.
     """
     super(HttpRuntimeProxy, self).__init__()
-    self._host = 'localhost'
-    self._port = None
     self._process = None
     self._process_lock = threading.Lock()  # Lock to guard self._process.
-    self._prior_error = None
     self._stderr_tee = None
     self._runtime_config_getter = runtime_config_getter
     self._args = args
@@ -153,14 +144,16 @@
     if start_process_flavor not in self._VALID_START_PROCESS_FLAVORS:
       raise ValueError('Invalid start_process_flavor.')
     self._start_process_flavor = start_process_flavor
+    self._proxy = None
 
-  def _get_error_file(self):
-    for error_handler in self._module_configuration.error_handlers or []:
-      if not error_handler.error_code or error_handler.error_code == 'default':
-        return os.path.join(self._module_configuration.application_root,
-                            error_handler.file)
-    else:
-      return None
+  def _get_instance_logs(self):
+    # Give the runtime process a bit of time to write to stderr.
+    time.sleep(0.1)
+    return self._stderr_tee.get_buf()
+
+  def _instance_died_unexpectedly(self):
+    with self._process_lock:
+      return self._process and self._process.poll() is not None
 
   def handle(self, environ, start_response, url_map, match, request_id,
              request_type):
@@ -179,145 +172,9 @@
     Yields:
       A sequence of strings containing the body of the HTTP response.
     """
-    if self._prior_error:
-      yield self._handle_error(self._prior_error, start_response)
-      return
 
-    environ[http_runtime_constants.SCRIPT_HEADER] = match.expand(url_map.script)
-    if request_type == instance.BACKGROUND_REQUEST:
-      environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'background'
-    elif request_type == instance.SHUTDOWN_REQUEST:
-      environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'shutdown'
-    elif request_type == instance.INTERACTIVE_REQUEST:
-      environ[http_runtime_constants.REQUEST_TYPE_HEADER] = 'interactive'
-
-    for name in http_runtime_constants.ENVIRONS_TO_PROPAGATE:
-      if http_runtime_constants.INTERNAL_ENVIRON_PREFIX + name not in environ:
-        value = environ.get(name, None)
-        if value is not None:
-          environ[
-              http_runtime_constants.INTERNAL_ENVIRON_PREFIX + name] = value
-    headers = util.get_headers_from_environ(environ)
-    if environ.get('QUERY_STRING'):
-      url = '%s?%s' % (urllib.quote(environ['PATH_INFO']),
-                       environ['QUERY_STRING'])
-    else:
-      url = urllib.quote(environ['PATH_INFO'])
-    if 'CONTENT_LENGTH' in environ:
-      headers['CONTENT-LENGTH'] = environ['CONTENT_LENGTH']
-      data = environ['wsgi.input'].read(int(environ['CONTENT_LENGTH']))
-    else:
-      data = ''
-
-    cookies = environ.get('HTTP_COOKIE')
-    user_email, admin, user_id = login.get_user_info(cookies)
-    if user_email:
-      nickname, organization = user_email.split('@', 1)
-    else:
-      nickname = ''
-      organization = ''
-    headers[http_runtime_constants.REQUEST_ID_HEADER] = request_id
-    headers[http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Id'] = (
-        user_id)
-    headers[http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Email'] = (
-        user_email)
-    headers[
-        http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Is-Admin'] = (
-            str(int(admin)))
-    headers[
-        http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Nickname'] = (
-            nickname)
-    headers[
-        http_runtime_constants.INTERNAL_HEADER_PREFIX + 'User-Organization'] = (
-            organization)
-    headers['X-AppEngine-Country'] = 'ZZ'
-    connection = httplib.HTTPConnection(self._host, self._port)
-    with contextlib.closing(connection):
-      try:
-        connection.connect()
-        connection.request(environ.get('REQUEST_METHOD', 'GET'),
-                           url,
-                           data,
-                           dict(headers.items()))
-
-        try:
-          response = connection.getresponse()
-        except httplib.HTTPException as e:
-          # The runtime process has written a bad HTTP response. For example,
-          # a Go runtime process may have crashed in app-specific code.
-          yield self._handle_error(
-              'the runtime process gave a bad HTTP response: %s' % e,
-              start_response)
-          return
-
-        # Ensures that we avoid merging repeat headers into a single header,
-        # allowing use of multiple Set-Cookie headers.
-        headers = []
-        for name in response.msg:
-          for value in response.msg.getheaders(name):
-            headers.append((name, value))
-
-        response_headers = wsgiref.headers.Headers(headers)
-
-        error_file = self._get_error_file()
-        if (error_file and
-            http_runtime_constants.ERROR_CODE_HEADER in response_headers):
-          try:
-            with open(error_file) as f:
-              content = f.read()
-          except IOError:
-            content = 'Failed to load error handler'
-            logging.exception('failed to load error file: %s', error_file)
-          start_response('500 Internal Server Error',
-                         [('Content-Type', 'text/html'),
-                          ('Content-Length', str(len(content)))])
-          yield content
-          return
-        del response_headers[http_runtime_constants.ERROR_CODE_HEADER]
-        start_response('%s %s' % (response.status, response.reason),
-                       response_headers.items())
-
-        # Yield the response body in small blocks.
-        while True:
-          try:
-            block = response.read(512)
-            if not block:
-              break
-            yield block
-          except httplib.HTTPException:
-            # The runtime process has encountered a problem, but has not
-            # necessarily crashed. For example, a Go runtime process' HTTP
-            # handler may have panicked in app-specific code (which the http
-            # package will recover from, so the process as a whole doesn't
-            # crash). At this point, we have already proxied onwards the HTTP
-            # header, so we cannot retroactively serve a 500 Internal Server
-            # Error. We silently break here; the runtime process has presumably
-            # already written to stderr (via the Tee).
-            break
-      except Exception:
-        with self._process_lock:
-          if self._process and self._process.poll() is not None:
-            # The development server is in a bad state. Log and return an error
-            # message.
-            self._prior_error = ('the runtime process for the instance running '
-                                 'on port %d has unexpectedly quit' % (
-                                     self._port))
-            yield self._handle_error(self._prior_error, start_response)
-          else:
-            raise
-
-  def _handle_error(self, message, start_response):
-    # Give the runtime process a bit of time to write to stderr.
-    time.sleep(0.1)
-    buf = self._stderr_tee.get_buf()
-    if buf:
-      message = message + '\n\n' + buf
-    # TODO: change 'text/plain' to 'text/plain; charset=utf-8'
-    # throughout devappserver2.
-    start_response('500 Internal Server Error',
-                   [('Content-Type', 'text/plain'),
-                    ('Content-Length', str(len(message)))])
-    return message
+    return self._proxy.handle(environ, start_response, url_map, match,
+                              request_id, request_type)
 
   def _read_start_process_file(self, max_attempts=10, sleep_base=.125):
     """Read the single line response expected in the start process file.
@@ -395,28 +252,22 @@
     if self._stderr_tee is None:
       self._stderr_tee = tee.Tee(self._process.stderr, sys.stderr)
       self._stderr_tee.start()
-    self._prior_error = None
-    self._port = None
-    try:
-      self._port = int(line)
-    except ValueError:
-      self._prior_error = 'bad runtime process port [%r]' % line
-      logging.error(self._prior_error)
-    else:
-      # Check if the runtime can serve requests.
-      if not self._can_connect():
-        self._prior_error = 'cannot connect to runtime on port %r' % self._port
-        logging.error(self._prior_error)
 
-  def _can_connect(self):
-    connection = httplib.HTTPConnection(self._host, self._port)
-    with contextlib.closing(connection):
-      try:
-        connection.connect()
-      except socket.error:
-        return False
-      else:
-        return True
+    port = None
+    error = None
+    try:
+      port = int(line)
+    except ValueError:
+      error = 'bad runtime process port [%r]' % line
+      logging.error(error)
+    finally:
+      self._proxy = http_proxy.HttpProxy(
+          host='localhost', port=port,
+          instance_died_unexpectedly=self._instance_died_unexpectedly,
+          instance_logs_getter=self._get_instance_logs,
+          error_handler_file=application_configuration.get_app_error_file(
+              self._module_configuration),
+          prior_error=error)
 
   def quit(self):
     """Causes the runtime process to exit."""
diff --git a/google/appengine/tools/devappserver2/http_runtime_constants.py b/google/appengine/tools/devappserver2/http_runtime_constants.py
index d8fdd4a..19b1312 100644
--- a/google/appengine/tools/devappserver2/http_runtime_constants.py
+++ b/google/appengine/tools/devappserver2/http_runtime_constants.py
@@ -19,12 +19,17 @@
 
 SERVER_SOFTWARE = 'Development/2.0'
 
-INTERNAL_HEADER_PREFIX = 'X-Appengine-Internal-'
-INTERNAL_ENVIRON_PREFIX = 'HTTP_X_APPENGINE_INTERNAL_'
+# Internal AppEngine prefix for Headers (Environment variables)
+# used in production. See apphosting/base/http_proto.cc for the full list.
+APPENGINE_HEADER_PREFIX = 'X-Appengine-'
+APPENGINE_ENVIRON_PREFIX = 'HTTP_X_APPENGINE_'
 
-REQUEST_ID_HEADER = 'X-Appengine-Internal-Request-Id'
-REQUEST_ID_ENVIRON = 'HTTP_X_APPENGINE_INTERNAL_REQUEST_ID'
+# Prefix for Headers (Environment variables) used in Dev AppServer only.
+APPENGINE_DEV_HEADER_PREFIX = APPENGINE_HEADER_PREFIX + 'Dev-'
+APPENGINE_DEV_ENVIRON_PREFIX = APPENGINE_ENVIRON_PREFIX + 'DEV_'
 
+# These values are passed as part of UPRequest proto in Prod.
+# Propagation rule: Cut the prefix.
 ENVIRONS_TO_PROPAGATE = set([
     'BACKEND_ID',
     'DEFAULT_VERSION_HOSTNAME',
@@ -41,11 +46,17 @@
     'SERVER_PROTOCOL',
     ])
 
-SCRIPT_HEADER = INTERNAL_ENVIRON_PREFIX + 'SCRIPT'
+REQUEST_ID_HEADER = APPENGINE_DEV_HEADER_PREFIX + 'Request-Id'
+REQUEST_ID_ENVIRON = APPENGINE_DEV_ENVIRON_PREFIX + 'REQUEST_ID'
+
+# TODO: rename to SCRIPT_ENVIRON
+SCRIPT_HEADER = APPENGINE_DEV_ENVIRON_PREFIX + 'SCRIPT'
+
+# TODO: rename to REQUEST_TYPE_ENVIRON
 # A request header where the value is a string containing the request type, e.g.
 # background.
-REQUEST_TYPE_HEADER = INTERNAL_ENVIRON_PREFIX + 'REQUEST_TYPE'
+REQUEST_TYPE_HEADER = APPENGINE_DEV_ENVIRON_PREFIX + 'REQUEST_TYPE'
 
 # A response header used by the runtime to indicate that an uncaught error has
 # ocurred and that a user-specified error handler should be used if available.
-ERROR_CODE_HEADER = '%sError-Code' % INTERNAL_HEADER_PREFIX
+ERROR_CODE_HEADER = '%sError-Code' % APPENGINE_HEADER_PREFIX
diff --git a/google/appengine/tools/devappserver2/http_runtime_test.py b/google/appengine/tools/devappserver2/http_runtime_test.py
index 64d5bdf..b306684 100644
--- a/google/appengine/tools/devappserver2/http_runtime_test.py
+++ b/google/appengine/tools/devappserver2/http_runtime_test.py
@@ -18,12 +18,9 @@
 
 
 import base64
-import cStringIO
-import httplib
 import os
 import re
 import shutil
-import socket
 import subprocess
 import tempfile
 import time
@@ -35,7 +32,6 @@
 
 from google.appengine.api import appinfo
 from google.appengine.tools.devappserver2 import http_runtime
-from google.appengine.tools.devappserver2 import http_runtime_constants
 from google.appengine.tools.devappserver2 import instance
 from google.appengine.tools.devappserver2 import login
 from google.appengine.tools.devappserver2 import runtime_config_pb2
@@ -124,11 +120,8 @@
     self.process.stdin = self.mox.CreateMockAnything()
     self.process.stdout = self.mox.CreateMockAnything()
     self.process.stderr = self.mox.CreateMockAnything()
+
     self.mox.StubOutWithMock(safe_subprocess, 'start_process')
-    self.mox.StubOutWithMock(httplib.HTTPConnection, 'connect')
-    self.mox.StubOutWithMock(httplib.HTTPConnection, 'request')
-    self.mox.StubOutWithMock(httplib.HTTPConnection, 'getresponse')
-    self.mox.StubOutWithMock(httplib.HTTPConnection, 'close')
     self.mox.StubOutWithMock(login, 'get_user_info')
     self.url_map = appinfo.URLMap(url=r'/(get|post).*',
                                   script=r'\1.py')
@@ -137,403 +130,6 @@
     shutil.rmtree(self.tmpdir)
     self.mox.UnsetStubs()
 
-  def test_handle_get(self):
-    response = FakeHttpResponse(200,
-                                'OK',
-                                [('Foo', 'a'), ('Foo', 'b'), ('Var', 'c')],
-                                'response')
-    login.get_user_info(None).AndReturn(('', False, ''))
-    httplib.HTTPConnection.connect()
-    httplib.HTTPConnection.request(
-        'GET', '/get%20request?key=value', '',
-        {'HEADER': 'value',
-         http_runtime_constants.REQUEST_ID_HEADER: 'request id',
-         'X-AppEngine-Country': 'ZZ',
-         'X-Appengine-Internal-User-Email': '',
-         'X-Appengine-Internal-User-Id': '',
-         'X-Appengine-Internal-User-Is-Admin': '0',
-         'X-Appengine-Internal-User-Nickname': '',
-         'X-Appengine-Internal-User-Organization': '',
-         'X-APPENGINE-INTERNAL-SCRIPT': 'get.py',
-         'X-APPENGINE-INTERNAL-SERVER-NAME': 'localhost',
-         'X-APPENGINE-INTERNAL-SERVER-PORT': '8080',
-         'X-APPENGINE-INTERNAL-SERVER-PROTOCOL': 'HTTP/1.1',
-        })
-    httplib.HTTPConnection.getresponse().AndReturn(response)
-    httplib.HTTPConnection.close()
-    environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get request',
-               'QUERY_STRING': 'key=value',
-               'HTTP_X_APPENGINE_INTERNAL_USER_ID': '123',
-               'SERVER_NAME': 'localhost',
-               'SERVER_PORT': '8080',
-               'SERVER_PROTOCOL': 'HTTP/1.1',
-              }
-    self.mox.ReplayAll()
-    expected_headers = [('Foo', 'a'), ('Foo', 'b'), ('Var', 'c')]
-    self.assertResponse('200 OK', expected_headers, 'response',
-                        self.proxy.handle, environ,
-                        url_map=self.url_map,
-                        match=re.match(self.url_map.url, '/get%20request'),
-                        request_id='request id',
-                        request_type=instance.NORMAL_REQUEST)
-    self.mox.VerifyAll()
-
-  def test_handle_post(self):
-    response = FakeHttpResponse(200,
-                                'OK',
-                                [('Foo', 'a'), ('Foo', 'b'), ('Var', 'c')],
-                                'response')
-    login.get_user_info('cookie').AndReturn(('user@example.com', True, '12345'))
-    httplib.HTTPConnection.connect()
-    httplib.HTTPConnection.request(
-        'POST', '/post', 'post data',
-        {'HEADER': 'value',
-         'COOKIE': 'cookie',
-         'CONTENT-TYPE': 'text/plain',
-         'CONTENT-LENGTH': '9',
-         http_runtime_constants.REQUEST_ID_HEADER: 'request id',
-         'X-AppEngine-Country': 'ZZ',
-         'X-Appengine-Internal-User-Email': 'user@example.com',
-         'X-Appengine-Internal-User-Id': '12345',
-         'X-Appengine-Internal-User-Is-Admin': '1',
-         'X-Appengine-Internal-User-Nickname': 'user',
-         'X-Appengine-Internal-User-Organization': 'example.com',
-         'X-APPENGINE-INTERNAL-SCRIPT': 'post.py',
-         'X-APPENGINE-INTERNAL-SERVER-NAME': 'localhost',
-         'X-APPENGINE-INTERNAL-SERVER-PORT': '8080',
-         'X-APPENGINE-INTERNAL-SERVER-PROTOCOL': 'HTTP/1.1',
-        })
-    httplib.HTTPConnection.getresponse().AndReturn(response)
-    httplib.HTTPConnection.close()
-    environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/post',
-               'wsgi.input': cStringIO.StringIO('post data'),
-               'CONTENT_LENGTH': '9',
-               'CONTENT_TYPE': 'text/plain',
-               'REQUEST_METHOD': 'POST',
-               'HTTP_COOKIE': 'cookie',
-               'SERVER_NAME': 'localhost',
-               'SERVER_PORT': '8080',
-               'SERVER_PROTOCOL': 'HTTP/1.1',
-              }
-    self.mox.ReplayAll()
-    expected_headers = [('Foo', 'a'), ('Foo', 'b'), ('Var', 'c')]
-    self.assertResponse('200 OK', expected_headers, 'response',
-                        self.proxy.handle, environ,
-                        url_map=self.url_map,
-                        match=re.match(self.url_map.url, '/post'),
-                        request_id='request id',
-                        request_type=instance.NORMAL_REQUEST)
-    self.mox.VerifyAll()
-
-  def test_handle_with_error(self):
-    with open(os.path.join(self.tmpdir, 'error.html'), 'w') as f:
-      f.write('error')
-    response = FakeHttpResponse(
-        500, 'Internal Server Error',
-        [(http_runtime_constants.ERROR_CODE_HEADER, '1')], '')
-    login.get_user_info(None).AndReturn(('', False, ''))
-    httplib.HTTPConnection.connect()
-    httplib.HTTPConnection.request(
-        'GET', '/get%20error', '',
-        {'HEADER': 'value',
-         http_runtime_constants.REQUEST_ID_HEADER: 'request id',
-         'X-AppEngine-Country': 'ZZ',
-         'X-Appengine-Internal-User-Email': '',
-         'X-Appengine-Internal-User-Id': '',
-         'X-Appengine-Internal-User-Is-Admin': '0',
-         'X-Appengine-Internal-User-Nickname': '',
-         'X-Appengine-Internal-User-Organization': '',
-         'X-APPENGINE-INTERNAL-SCRIPT': 'get.py',
-         'X-APPENGINE-INTERNAL-SERVER-NAME': 'localhost',
-         'X-APPENGINE-INTERNAL-SERVER-PORT': '8080',
-         'X-APPENGINE-INTERNAL-SERVER-PROTOCOL': 'HTTP/1.1',
-        })
-    httplib.HTTPConnection.getresponse().AndReturn(response)
-    httplib.HTTPConnection.close()
-    environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get error',
-               'QUERY_STRING': '',
-               'HTTP_X_APPENGINE_INTERNAL_USER_ID': '123',
-               'SERVER_NAME': 'localhost',
-               'SERVER_PORT': '8080',
-               'SERVER_PROTOCOL': 'HTTP/1.1',
-              }
-    self.mox.ReplayAll()
-    expected_headers = {
-        'Content-Type': 'text/html',
-        'Content-Length': '5',
-    }
-    self.assertResponse('500 Internal Server Error', expected_headers, 'error',
-                        self.proxy.handle, environ,
-                        url_map=self.url_map,
-                        match=re.match(self.url_map.url, '/get%20error'),
-                        request_id='request id',
-                        request_type=instance.NORMAL_REQUEST)
-    self.mox.VerifyAll()
-
-  def test_handle_with_error_no_error_handler(self):
-    self.proxy = http_runtime.HttpRuntimeProxy(
-        ['/runtime'], self.runtime_config_getter, appinfo.AppInfoExternal())
-    self.proxy._port = 23456
-    response = FakeHttpResponse(
-        500, 'Internal Server Error',
-        [(http_runtime_constants.ERROR_CODE_HEADER, '1')], '')
-    login.get_user_info(None).AndReturn(('', False, ''))
-    httplib.HTTPConnection.connect()
-    httplib.HTTPConnection.request(
-        'GET', '/get%20error', '',
-        {'HEADER': 'value',
-         http_runtime_constants.REQUEST_ID_HEADER: 'request id',
-         'X-AppEngine-Country': 'ZZ',
-         'X-Appengine-Internal-User-Email': '',
-         'X-Appengine-Internal-User-Id': '',
-         'X-Appengine-Internal-User-Is-Admin': '0',
-         'X-Appengine-Internal-User-Nickname': '',
-         'X-Appengine-Internal-User-Organization': '',
-         'X-APPENGINE-INTERNAL-SCRIPT': 'get.py',
-         'X-APPENGINE-INTERNAL-SERVER-NAME': 'localhost',
-         'X-APPENGINE-INTERNAL-SERVER-PORT': '8080',
-         'X-APPENGINE-INTERNAL-SERVER-PROTOCOL': 'HTTP/1.1',
-        })
-    httplib.HTTPConnection.getresponse().AndReturn(response)
-    httplib.HTTPConnection.close()
-    environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get error',
-               'QUERY_STRING': '',
-               'HTTP_X_APPENGINE_INTERNAL_USER_ID': '123',
-               'SERVER_NAME': 'localhost',
-               'SERVER_PORT': '8080',
-               'SERVER_PROTOCOL': 'HTTP/1.1',
-              }
-    self.mox.ReplayAll()
-    self.assertResponse('500 Internal Server Error', {}, '',
-                        self.proxy.handle, environ,
-                        url_map=self.url_map,
-                        match=re.match(self.url_map.url, '/get%20error'),
-                        request_id='request id',
-                        request_type=instance.NORMAL_REQUEST)
-    self.mox.VerifyAll()
-
-  def test_handle_with_error_missing_error_handler(self):
-    response = FakeHttpResponse(
-        500, 'Internal Server Error',
-        [(http_runtime_constants.ERROR_CODE_HEADER, '1')], '')
-    login.get_user_info(None).AndReturn(('', False, ''))
-    httplib.HTTPConnection.connect()
-    httplib.HTTPConnection.request(
-        'GET', '/get%20error', '',
-        {'HEADER': 'value',
-         http_runtime_constants.REQUEST_ID_HEADER: 'request id',
-         'X-AppEngine-Country': 'ZZ',
-         'X-Appengine-Internal-User-Email': '',
-         'X-Appengine-Internal-User-Id': '',
-         'X-Appengine-Internal-User-Is-Admin': '0',
-         'X-Appengine-Internal-User-Nickname': '',
-         'X-Appengine-Internal-User-Organization': '',
-         'X-APPENGINE-INTERNAL-SCRIPT': 'get.py',
-         'X-APPENGINE-INTERNAL-SERVER-NAME': 'localhost',
-         'X-APPENGINE-INTERNAL-SERVER-PORT': '8080',
-         'X-APPENGINE-INTERNAL-SERVER-PROTOCOL': 'HTTP/1.1',
-        })
-    httplib.HTTPConnection.getresponse().AndReturn(response)
-    httplib.HTTPConnection.close()
-    environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get error',
-               'QUERY_STRING': '',
-               'HTTP_X_APPENGINE_INTERNAL_USER_ID': '123',
-               'SERVER_NAME': 'localhost',
-               'SERVER_PORT': '8080',
-               'SERVER_PROTOCOL': 'HTTP/1.1',
-              }
-    self.mox.ReplayAll()
-    expected_headers = {
-        'Content-Type': 'text/html',
-        'Content-Length': '28',
-    }
-    self.assertResponse('500 Internal Server Error', expected_headers,
-                        'Failed to load error handler', self.proxy.handle,
-                        environ, url_map=self.url_map,
-                        match=re.match(self.url_map.url, '/get%20error'),
-                        request_id='request id',
-                        request_type=instance.NORMAL_REQUEST)
-    self.mox.VerifyAll()
-
-  def test_http_response_early_failure(self):
-    header = ('the runtime process gave a bad HTTP response: '
-              'IncompleteRead(0 bytes read)\n\n')
-    stderr0 = "I'm sorry, Dave. I'm afraid I can't do that.\n"
-    self.proxy._stderr_tee = FakeTee(stderr0)
-    login.get_user_info(None).AndReturn(('', False, ''))
-    httplib.HTTPConnection.connect()
-    httplib.HTTPConnection.request(
-        'GET', '/get%20request?key=value', '',
-        {'HEADER': 'value',
-         http_runtime_constants.REQUEST_ID_HEADER: 'request id',
-         'X-AppEngine-Country': 'ZZ',
-         'X-Appengine-Internal-User-Email': '',
-         'X-Appengine-Internal-User-Id': '',
-         'X-Appengine-Internal-User-Is-Admin': '0',
-         'X-Appengine-Internal-User-Nickname': '',
-         'X-Appengine-Internal-User-Organization': '',
-         'X-APPENGINE-INTERNAL-SCRIPT': 'get.py',
-         'X-APPENGINE-INTERNAL-SERVER-NAME': 'localhost',
-         'X-APPENGINE-INTERNAL-SERVER-PORT': '8080',
-         'X-APPENGINE-INTERNAL-SERVER-PROTOCOL': 'HTTP/1.1',
-        })
-    httplib.HTTPConnection.getresponse().AndRaise(httplib.IncompleteRead(''))
-    httplib.HTTPConnection.close()
-    environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get request',
-               'QUERY_STRING': 'key=value',
-               'HTTP_X_APPENGINE_INTERNAL_USER_ID': '123',
-               'SERVER_NAME': 'localhost',
-               'SERVER_PORT': '8080',
-               'SERVER_PROTOCOL': 'HTTP/1.1',
-              }
-    self.mox.ReplayAll()
-    expected_headers = {
-        'Content-Type': 'text/plain',
-        'Content-Length': '121',#str(len(header) + len(stderr0)),
-    }
-    self.assertResponse('500 Internal Server Error', expected_headers,
-                        header + stderr0,
-                        self.proxy.handle, environ,
-                        url_map=self.url_map,
-                        match=re.match(self.url_map.url, '/get%20request'),
-                        request_id='request id',
-                        request_type=instance.NORMAL_REQUEST)
-    self.mox.VerifyAll()
-
-  def test_http_response_late_failure(self):
-    line0 = "I know I've made some very poor decisions recently...\n"
-    line1 = "I'm afraid. I'm afraid, Dave.\n"
-    line2 = "Dave, my mind is going. I can feel it.\n"
-    response = FakeHttpResponse(200, 'OK', [], line0)
-    response.partial_read_error = httplib.IncompleteRead('')
-    self.proxy._stderr_tee = FakeTee(line1 + line2)
-    login.get_user_info(None).AndReturn(('', False, ''))
-    httplib.HTTPConnection.connect()
-    httplib.HTTPConnection.request(
-        'GET', '/get%20request?key=value', '',
-        {'HEADER': 'value',
-         http_runtime_constants.REQUEST_ID_HEADER: 'request id',
-         'X-AppEngine-Country': 'ZZ',
-         'X-Appengine-Internal-User-Email': '',
-         'X-Appengine-Internal-User-Id': '',
-         'X-Appengine-Internal-User-Is-Admin': '0',
-         'X-Appengine-Internal-User-Nickname': '',
-         'X-Appengine-Internal-User-Organization': '',
-         'X-APPENGINE-INTERNAL-SCRIPT': 'get.py',
-         'X-APPENGINE-INTERNAL-SERVER-NAME': 'localhost',
-         'X-APPENGINE-INTERNAL-SERVER-PORT': '8080',
-         'X-APPENGINE-INTERNAL-SERVER-PROTOCOL': 'HTTP/1.1',
-        })
-    httplib.HTTPConnection.getresponse().AndReturn(response)
-    httplib.HTTPConnection.close()
-    environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get request',
-               'QUERY_STRING': 'key=value',
-               'HTTP_X_APPENGINE_INTERNAL_USER_ID': '123',
-               'SERVER_NAME': 'localhost',
-               'SERVER_PORT': '8080',
-               'SERVER_PROTOCOL': 'HTTP/1.1',
-              }
-    self.mox.ReplayAll()
-    self.assertResponse('200 OK', {},
-                        line0,
-                        self.proxy.handle, environ,
-                        url_map=self.url_map,
-                        match=re.match(self.url_map.url, '/get%20request'),
-                        request_id='request id',
-                        request_type=instance.NORMAL_REQUEST)
-    self.mox.VerifyAll()
-
-  def test_connection_error(self):
-    self.proxy = http_runtime.HttpRuntimeProxy(
-        ['/runtime'], self.runtime_config_getter, appinfo.AppInfoExternal())
-    self.proxy._process = self.mox.CreateMockAnything()
-    self.proxy._port = 23456
-    login.get_user_info(None).AndReturn(('', False, ''))
-    httplib.HTTPConnection.connect().AndRaise(socket.error())
-    self.proxy._process.poll().AndReturn(None)
-    httplib.HTTPConnection.close()
-
-    self.mox.ReplayAll()
-    self.assertRaises(socket.error,
-                      self.proxy.handle(
-                          {'PATH_INFO': '/'},
-                          start_response=None,  # Not used.
-                          url_map=self.url_map,
-                          match=re.match(self.url_map.url, '/get%20error'),
-                          request_id='request id',
-                          request_type=instance.NORMAL_REQUEST).next)
-    self.mox.VerifyAll()
-
-  def test_connection_error_process_quit(self):
-    self.proxy = http_runtime.HttpRuntimeProxy(
-        ['/runtime'], self.runtime_config_getter, appinfo.AppInfoExternal())
-    self.proxy._process = self.mox.CreateMockAnything()
-    self.proxy._port = 123
-    login.get_user_info(None).AndReturn(('', False, ''))
-    httplib.HTTPConnection.connect().AndRaise(socket.error())
-    self.proxy._process.poll().AndReturn(1)
-    self.proxy._stderr_tee = FakeTee('')
-    httplib.HTTPConnection.close()
-
-    self.mox.ReplayAll()
-    expected_headers = {
-        'Content-Type': 'text/plain',
-        'Content-Length': '78',
-    }
-    expected_content = ('the runtime process for the instance running on port '
-                        '123 has unexpectedly quit')
-    self.assertResponse('500 Internal Server Error',
-                        expected_headers,
-                        expected_content,
-                        self.proxy.handle,
-                        {'PATH_INFO': '/'},
-                        url_map=self.url_map,
-                        match=re.match(self.url_map.url, '/get%20error'),
-                        request_id='request id',
-                        request_type=instance.NORMAL_REQUEST)
-    self.mox.VerifyAll()
-
-  def test_handle_background_thread(self):
-    response = FakeHttpResponse(200, 'OK', [('Foo', 'Bar')], 'response')
-    login.get_user_info(None).AndReturn(('', False, ''))
-    httplib.HTTPConnection.connect()
-    httplib.HTTPConnection.request(
-        'GET', '/get%20request?key=value', '',
-        {'HEADER': 'value',
-         http_runtime_constants.REQUEST_ID_HEADER: 'request id',
-         'X-AppEngine-Country': 'ZZ',
-         'X-Appengine-Internal-User-Email': '',
-         'X-Appengine-Internal-User-Id': '',
-         'X-Appengine-Internal-User-Is-Admin': '0',
-         'X-Appengine-Internal-User-Nickname': '',
-         'X-Appengine-Internal-User-Organization': '',
-         'X-APPENGINE-INTERNAL-SCRIPT': 'get.py',
-         'X-APPENGINE-INTERNAL-REQUEST-TYPE': 'background',
-         'X-APPENGINE-INTERNAL-SERVER-NAME': 'localhost',
-         'X-APPENGINE-INTERNAL-SERVER-PORT': '8080',
-         'X-APPENGINE-INTERNAL-SERVER-PROTOCOL': 'HTTP/1.1',
-        })
-    httplib.HTTPConnection.getresponse().AndReturn(response)
-    httplib.HTTPConnection.close()
-    environ = {'HTTP_HEADER': 'value', 'PATH_INFO': '/get request',
-               'QUERY_STRING': 'key=value',
-               'HTTP_X_APPENGINE_INTERNAL_USER_ID': '123',
-               'SERVER_NAME': 'localhost',
-               'SERVER_PORT': '8080',
-               'SERVER_PROTOCOL': 'HTTP/1.1',
-              }
-    self.mox.ReplayAll()
-    expected_headers = {
-        'Foo': 'Bar',
-    }
-    self.assertResponse('200 OK', expected_headers, 'response',
-                        self.proxy.handle, environ,
-                        url_map=self.url_map,
-                        match=re.match(self.url_map.url, '/get%20request'),
-                        request_id='request id',
-                        request_type=instance.BACKGROUND_REQUEST)
-    self.mox.VerifyAll()
-
   def test_start_and_quit(self):
     ## Test start()
     # start()
@@ -547,10 +143,6 @@
     self.process.stdout.readline().AndReturn('30000')
     self.proxy._stderr_tee = FakeTee('')
 
-    # _can_connect() via start().
-    httplib.HTTPConnection.connect()
-    httplib.HTTPConnection.close()
-
     self.mox.ReplayAll()
     self.proxy.start()
     self.mox.VerifyAll()
@@ -591,35 +183,6 @@
                         request_type=instance.NORMAL_REQUEST)
     self.mox.VerifyAll()
 
-  def test_start_and_not_serving(self):
-    safe_subprocess.start_process(
-        ['/runtime'],
-        base64.b64encode(self.runtime_config.SerializeToString()),
-        stdout=subprocess.PIPE,
-        stderr=subprocess.PIPE,
-        env={'foo': 'bar'},
-        cwd=self.tmpdir).AndReturn(self.process)
-    self.process.stdout.readline().AndReturn('30002')
-    self.proxy._stderr_tee = FakeTee('')
-
-    httplib.HTTPConnection.connect().AndRaise(socket.error)
-    httplib.HTTPConnection.close()
-
-    self.mox.ReplayAll()
-    self.proxy.start()
-    expected_headers = {
-        'Content-Type': 'text/plain',
-        'Content-Length': '39',
-    }
-    self.assertResponse('500 Internal Server Error', expected_headers,
-                        'cannot connect to runtime on port 30002',
-                        self.proxy.handle, {},
-                        url_map=self.url_map,
-                        match=re.match(self.url_map.url, '/get%20request'),
-                        request_id='request id',
-                        request_type=instance.NORMAL_REQUEST)
-    self.mox.VerifyAll()
-
 
 class HttpRuntimeProxyFileFlavorTest(wsgi_test_utils.WSGITestCase):
   def setUp(self):
@@ -639,7 +202,6 @@
         ['/runtime'], self.runtime_config_getter, module_configuration,
         env={'foo': 'bar'},
         start_process_flavor=http_runtime.START_PROCESS_FILE)
-    self.proxy._port = 23456
     self.mox.StubOutWithMock(self.proxy, '_process_lock')
     self.process = self.mox.CreateMock(subprocess.Popen)
     self.process.stdin = self.mox.CreateMockAnything()
@@ -647,10 +209,6 @@
     self.process.stderr = self.mox.CreateMockAnything()
     self.process.child_out = self.mox.CreateMockAnything()
     self.mox.StubOutWithMock(safe_subprocess, 'start_process_file')
-    self.mox.StubOutWithMock(httplib.HTTPConnection, 'connect')
-    self.mox.StubOutWithMock(httplib.HTTPConnection, 'request')
-    self.mox.StubOutWithMock(httplib.HTTPConnection, 'getresponse')
-    self.mox.StubOutWithMock(httplib.HTTPConnection, 'close')
     self.mox.StubOutWithMock(os, 'remove')
     self.mox.StubOutWithMock(time, 'sleep')
     self.url_map = appinfo.URLMap(url=r'/(get|post).*',
@@ -679,13 +237,9 @@
     os.remove('/tmp/c-out.ABC').AndReturn(None)
     self.proxy._stderr_tee = FakeTee('')
 
-    # _can_connect() via start().
-    httplib.HTTPConnection.connect()
-    httplib.HTTPConnection.close()
-
     self.mox.ReplayAll()
     self.proxy.start()
-    self.assertEquals(1234, self.proxy._port)
+    self.assertEquals(1234, self.proxy._proxy._port)
     self.mox.VerifyAll()
 
   def test_slow_shattered(self):
@@ -711,13 +265,9 @@
     os.remove('/tmp/c-out.ABC').AndReturn(None)
     self.proxy._stderr_tee = FakeTee('')
 
-    # _can_connect() via start().
-    httplib.HTTPConnection.connect()
-    httplib.HTTPConnection.close()
-
     self.mox.ReplayAll()
     self.proxy.start()
-    self.assertEquals(4321, self.proxy._port)
+    self.assertEquals(4321, self.proxy._proxy._port)
     self.mox.VerifyAll()
 
   def test_runtime_instance_dies_immediately(self):
diff --git a/google/appengine/tools/devappserver2/module.py b/google/appengine/tools/devappserver2/module.py
index 75d148c..9588f53 100644
--- a/google/appengine/tools/devappserver2/module.py
+++ b/google/appengine/tools/devappserver2/module.py
@@ -64,6 +64,7 @@
 from google.appengine.tools.devappserver2 import thread_executor
 from google.appengine.tools.devappserver2 import url_handler
 from google.appengine.tools.devappserver2 import util
+from google.appengine.tools.devappserver2 import vm_runtime_proxy
 from google.appengine.tools.devappserver2 import wsgi_handler
 from google.appengine.tools.devappserver2 import wsgi_server
 
@@ -147,6 +148,7 @@
       'php': php_runtime.PHPRuntimeInstanceFactory,
       'python': python_runtime.PythonRuntimeInstanceFactory,
       'python27': python_runtime.PythonRuntimeInstanceFactory,
+      'vm': vm_runtime_proxy.VMRuntimeInstanceFactory,
   }
   if java_runtime:
     _RUNTIME_INSTANCE_FACTORIES.update({
@@ -296,6 +298,9 @@
         self._module_configuration.runtime.startswith('python')):
       runtime_config.python_config.CopyFrom(self._python_config)
 
+    if self._vm_config:
+      runtime_config.vm_config.CopyFrom(self._vm_config)
+
     return runtime_config
 
   def _maybe_restart_instances(self, config_changed, file_changed):
@@ -359,6 +364,7 @@
                php_config,
                python_config,
                cloud_sql_config,
+               vm_config,
                default_version_port,
                port_registry,
                request_data,
@@ -369,7 +375,6 @@
                allow_skipped_files,
                threadsafe_override):
     """Initializer for Module.
-
     Args:
       module_configuration: An application_configuration.ModuleConfiguration
           instance storing the configuration data for a module.
@@ -392,6 +397,9 @@
       cloud_sql_config: A runtime_config_pb2.CloudSQL instance containing the
           required configuration for local Google Cloud SQL development. If None
           then Cloud SQL will not be available.
+      vm_config: A runtime_config_pb2.VMConfig instance containing
+          VM runtime-specific configuration. If None all docker-related stuff
+          is disabled.
       default_version_port: An int containing the port of the default version.
       port_registry: A dispatcher.PortRegistry used to provide the Dispatcher
           with a mapping of port to Module and Instance.
@@ -422,6 +430,7 @@
     self._php_config = php_config
     self._python_config = python_config
     self._cloud_sql_config = cloud_sql_config
+    self._vm_config = vm_config
     self._request_data = request_data
     self._allow_skipped_files = allow_skipped_files
     self._threadsafe_override = threadsafe_override
@@ -762,6 +771,7 @@
                                       self._php_config,
                                       self._python_config,
                                       self._cloud_sql_config,
+                                      self._vm_config,
                                       self._default_version_port,
                                       self._port_registry,
                                       self._request_data,
@@ -865,6 +875,7 @@
                php_config,
                python_config,
                cloud_sql_config,
+               unused_vm_config,
                default_version_port,
                port_registry,
                request_data,
@@ -898,6 +909,9 @@
       cloud_sql_config: A runtime_config_pb2.CloudSQL instance containing the
           required configuration for local Google Cloud SQL development. If None
           then Cloud SQL will not be available.
+      unused_vm_config: A runtime_config_pb2.VMConfig instance containing
+          VM runtime-specific configuration. Ignored by AutoScalingModule as
+          autoscaling is not yet supported by VM runtimes.
       default_version_port: An int containing the port of the default version.
       port_registry: A dispatcher.PortRegistry used to provide the Dispatcher
           with a mapping of port to Module and Instance.
@@ -927,6 +941,9 @@
                                             php_config,
                                             python_config,
                                             cloud_sql_config,
+                                            # VM runtimes does not support
+                                            # autoscaling.
+                                            None,
                                             default_version_port,
                                             port_registry,
                                             request_data,
@@ -937,6 +954,7 @@
                                             allow_skipped_files,
                                             threadsafe_override)
 
+
     self._process_automatic_scaling(
         self._module_configuration.automatic_scaling)
 
@@ -1300,6 +1318,7 @@
                php_config,
                python_config,
                cloud_sql_config,
+               vm_config,
                default_version_port,
                port_registry,
                request_data,
@@ -1309,6 +1328,7 @@
                automatic_restarts,
                allow_skipped_files,
                threadsafe_override):
+
     """Initializer for ManualScalingModule.
 
     Args:
@@ -1333,6 +1353,9 @@
       cloud_sql_config: A runtime_config_pb2.CloudSQL instance containing the
           required configuration for local Google Cloud SQL development. If None
           then Cloud SQL will not be available.
+      vm_config: A runtime_config_pb2.VMConfig instance containing
+          VM runtime-specific configuration. If None all docker-related stuff
+          is disabled.
       default_version_port: An int containing the port of the default version.
       port_registry: A dispatcher.PortRegistry used to provide the Dispatcher
           with a mapping of port to Module and Instance.
@@ -1362,6 +1385,7 @@
                                               php_config,
                                               python_config,
                                               cloud_sql_config,
+                                              vm_config,
                                               default_version_port,
                                               port_registry,
                                               request_data,
@@ -1372,6 +1396,7 @@
                                               allow_skipped_files,
                                               threadsafe_override)
 
+
     self._process_manual_scaling(module_configuration.manual_scaling)
 
     self._instances = []  # Protected by self._condition.
@@ -1799,6 +1824,7 @@
                php_config,
                python_config,
                cloud_sql_config,
+               vm_config,
                default_version_port,
                port_registry,
                request_data,
@@ -1808,6 +1834,7 @@
                automatic_restarts,
                allow_skipped_files,
                threadsafe_override):
+
     """Initializer for BasicScalingModule.
 
     Args:
@@ -1832,6 +1859,9 @@
       cloud_sql_config: A runtime_config_pb2.CloudSQL instance containing the
           required configuration for local Google Cloud SQL development. If None
           then Cloud SQL will not be available.
+      vm_config: A runtime_config_pb2.VMConfig instance containing
+          VM runtime-specific configuration. If None all docker-related stuff
+          is disabled.
       default_version_port: An int containing the port of the default version.
       port_registry: A dispatcher.PortRegistry used to provide the Dispatcher
           with a mapping of port to Module and Instance.
@@ -1861,6 +1891,7 @@
                                              php_config,
                                              python_config,
                                              cloud_sql_config,
+                                             vm_config,
                                              default_version_port,
                                              port_registry,
                                              request_data,
@@ -1870,6 +1901,7 @@
                                              automatic_restarts,
                                              allow_skipped_files,
                                              threadsafe_override)
+
     self._process_basic_scaling(module_configuration.basic_scaling)
 
     self._instances = []  # Protected by self._condition.
@@ -2224,6 +2256,7 @@
                php_config,
                python_config,
                cloud_sql_config,
+               vm_config,
                default_version_port,
                port_registry,
                request_data,
@@ -2257,6 +2290,9 @@
       cloud_sql_config: A runtime_config_pb2.CloudSQL instance containing the
           required configuration for local Google Cloud SQL development. If None
           then Cloud SQL will not be available.
+      vm_config: A runtime_config_pb2.VMConfig instance containing
+          VM runtime-specific configuration. If None all docker-related stuff
+          is disabled.
       default_version_port: An int containing the port of the default version.
       port_registry: A dispatcher.PortRegistry used to provide the Dispatcher
           with a mapping of port to Module and Instance.
@@ -2283,6 +2319,7 @@
         php_config,
         python_config,
         cloud_sql_config,
+        vm_config,
         default_version_port,
         port_registry,
         request_data,
diff --git a/google/appengine/tools/devappserver2/module_test.py b/google/appengine/tools/devappserver2/module_test.py
index a350cd0..beb11eb 100644
--- a/google/appengine/tools/devappserver2/module_test.py
+++ b/google/appengine/tools/devappserver2/module_test.py
@@ -95,6 +95,7 @@
         php_config=None,
         python_config=None,
         cloud_sql_config=None,
+        vm_config=None,
         default_version_port=8080,
         port_registry=dispatcher.PortRegistry(),
         request_data=None,
@@ -135,6 +136,7 @@
         php_config=None,
         python_config=None,
         cloud_sql_config=None,
+        unused_vm_config=None,
         default_version_port=8080,
         port_registry=dispatcher.PortRegistry(),
         request_data=None,
@@ -174,6 +176,7 @@
         php_config=None,
         python_config=None,
         cloud_sql_config=None,
+        vm_config=None,
         default_version_port=8080,
         port_registry=dispatcher.PortRegistry(),
         request_data=None,
@@ -214,6 +217,7 @@
         php_config=None,
         python_config=None,
         cloud_sql_config=None,
+        vm_config=None,
         default_version_port=8080,
         port_registry=dispatcher.PortRegistry(),
         request_data=None,
@@ -2270,6 +2274,7 @@
         php_config=None,
         python_config=None,
         cloud_sql_config=None,
+        vm_config=None,
         default_version_port=8080,
         port_registry=dispatcher.PortRegistry(),
         request_data=None,
diff --git a/google/appengine/tools/devappserver2/php/runtime.py b/google/appengine/tools/devappserver2/php/runtime.py
index 6ec8852..2f6007b 100644
--- a/google/appengine/tools/devappserver2/php/runtime.py
+++ b/google/appengine/tools/devappserver2/php/runtime.py
@@ -30,6 +30,7 @@
 import google
 
 from google.appengine.api import appinfo
+from google.appengine.tools.devappserver2 import environ_utils
 from google.appengine.tools.devappserver2 import http_runtime_constants
 from google.appengine.tools.devappserver2 import php
 from google.appengine.tools.devappserver2 import request_rewriter
@@ -91,7 +92,7 @@
     """
     user_environ = self.environ_template.copy()
 
-    self.copy_headers(environ, user_environ)
+    environ_utils.propagate_environs(environ, user_environ)
     user_environ['REQUEST_METHOD'] = environ.get('REQUEST_METHOD', 'GET')
     user_environ['PATH_INFO'] = environ['PATH_INFO']
     user_environ['QUERY_STRING'] = environ['QUERY_STRING']
@@ -197,29 +198,6 @@
     start_response(status, headers)
     return [message.fp.read()]
 
-  def copy_headers(self, source_environ, dest_environ):
-    """Copy headers from source_environ to dest_environ.
-
-    This extracts headers that represent environ values and propagates all
-    other headers which are not used for internal implementation details or
-    headers that are stripped.
-
-    Args:
-      source_environ: The source environ dict.
-      dest_environ: The environ dict to populate.
-    """
-    # TODO: This method is copied from python/runtime.py. If this
-    # method isn't obsoleted, consider moving it to some sort of utility module.
-    for env in http_runtime_constants.ENVIRONS_TO_PROPAGATE:
-      value = source_environ.get(
-          http_runtime_constants.INTERNAL_ENVIRON_PREFIX + env, None)
-      if value is not None:
-        dest_environ[env] = value
-    for name, value in source_environ.items():
-      if (name.startswith('HTTP_') and
-          not name.startswith(http_runtime_constants.INTERNAL_ENVIRON_PREFIX)):
-        dest_environ[name] = value
-
 
 def main():
   config = runtime_config_pb2.Config()
diff --git a/google/appengine/tools/devappserver2/php/setup.php b/google/appengine/tools/devappserver2/php/setup.php
index 6155a7a..f27eeeb 100644
--- a/google/appengine/tools/devappserver2/php/setup.php
+++ b/google/appengine/tools/devappserver2/php/setup.php
@@ -7,6 +7,12 @@
   // TODO(bquinlan): Use the logs service to persist this message.
 }
 
+$unsetEnv = function($var_name) {
+  putenv($var_name);
+  unset($_ENV[$var_name]);
+  unset($_SERVER[$var_name]);
+};
+
 $setup = function() {
   $setupGaeExtension = function() {
     $allowed_buckets = '';
@@ -31,6 +37,10 @@
   };
 
   $updateScriptFilename = function() {
+    global $unsetEnv;
+    $_SERVER['DOCUMENT_ROOT'] = $_SERVER['APPLICATION_ROOT'];
+    $unsetEnv('APPLICATION_ROOT');
+
     putenv('SCRIPT_FILENAME=' . getenv('REAL_SCRIPT_FILENAME'));
     $_ENV['SCRIPT_FILENAME'] = getenv('REAL_SCRIPT_FILENAME');
 
@@ -42,42 +52,32 @@
     chdir($actualPath);
 
     $_SERVER['SCRIPT_FILENAME'] = getenv('REAL_SCRIPT_FILENAME');
-    putenv('REAL_SCRIPT_FILENAME');
-    unset($_ENV['REAL_SCRIPT_FILENAME']);
-    unset($_SERVER['REAL_SCRIPT_FILENAME']);
+    $unsetEnv('REAL_SCRIPT_FILENAME');
 
     // Replicate the SCRIPT_NAME and PHP_SELF setup used in production.
-    // Set SCRIPT_NAME to SCRIPT_FILENAME made relative to APPLICTION_ROOT and
+    // Set SCRIPT_NAME to SCRIPT_FILENAME made relative to DOCUMENT_ROOT and
     // PHP_SELF to SCRIPT_NAME except when the script is included in PATH_INFO (
     // REQUEST_URI without the query string) which matches Apache behavior.
     $_SERVER['SCRIPT_NAME'] = substr(
-      $_SERVER['SCRIPT_FILENAME'], strlen($_SERVER['APPLICATION_ROOT']));
+      $_SERVER['SCRIPT_FILENAME'], strlen($_SERVER['DOCUMENT_ROOT']));
     if (strpos($_SERVER['PATH_INFO'], $_SERVER['SCRIPT_NAME']) === 0) {
       $_SERVER['PHP_SELF'] = $_SERVER['PATH_INFO'];
     } else {
       $_SERVER['PHP_SELF'] = $_SERVER['SCRIPT_NAME'];
     }
-
-    unset($_ENV['APPLICATION_ROOT']);
-    unset($_SERVER['APPLICATION_ROOT']);
   };
 
   $setupApiProxy = function() {
+    global $unsetEnv;
     require_once 'google/appengine/runtime/ApiProxy.php';
     require_once 'google/appengine/runtime/RemoteApiProxy.php';
     \google\appengine\runtime\ApiProxy::setApiProxy(
       new \google\appengine\runtime\RemoteApiProxy(
         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']);
+    $unsetEnv('REMOTE_API_HOST');
+    $unsetEnv('REMOTE_API_PORT');
+    $unsetEnv('REMOTE_REQUEST_ID');
   };
 
   $setupBuiltins = function() {
@@ -92,27 +92,12 @@
 $setup();
 unset($setup);
 
-$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);
+if (isset($_ENV['HTTP_X_APPENGINE_DEV_REQUEST_TYPE']) &&
+    $_ENV['HTTP_X_APPENGINE_DEV_REQUEST_TYPE'] == 'interactive') {
+  $unsetEnv('HTTP_X_APPENGINE_DEV_REQUEST_TYPE');
+  unset($unsetEnv);
   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.
+  unset($unsetEnv);
   require($_ENV['SCRIPT_FILENAME']);
 }
-
-
diff --git a/google/appengine/tools/devappserver2/python/request_handler.py b/google/appengine/tools/devappserver2/python/request_handler.py
index 6b2257a..5276a96 100644
--- a/google/appengine/tools/devappserver2/python/request_handler.py
+++ b/google/appengine/tools/devappserver2/python/request_handler.py
@@ -40,6 +40,7 @@
 from google.appengine.runtime import request_environment
 from google.appengine.runtime import runtime
 from google.appengine.runtime import shutdown
+from google.appengine.tools.devappserver2 import environ_utils
 from google.appengine.tools.devappserver2 import http_runtime_constants
 from google.appengine.tools.devappserver2.python import request_state
 
@@ -235,7 +236,7 @@
       A dict containing the environ representing an HTTP request.
     """
     user_environ = self.environ_template.copy()
-    self.copy_headers(environ, user_environ)
+    environ_utils.propagate_environs(environ, user_environ)
     user_environ['REQUEST_METHOD'] = environ.get('REQUEST_METHOD', 'GET')
     content_type = environ.get('CONTENT_TYPE')
     if content_type:
@@ -245,27 +246,6 @@
       user_environ['HTTP_CONTENT_LENGTH'] = content_length
     return user_environ
 
-  def copy_headers(self, source_environ, dest_environ):
-    """Copy headers from source_environ to dest_environ.
-
-    This extracts headers that represent environ values and propagates all
-    other headers which are not used for internal implementation details or
-    headers that are stripped.
-
-    Args:
-      source_environ: The source environ dict.
-      dest_environ: The environ dict to populate.
-    """
-    for env in http_runtime_constants.ENVIRONS_TO_PROPAGATE:
-      value = source_environ.get(
-          http_runtime_constants.INTERNAL_ENVIRON_PREFIX + env, None)
-      if value is not None:
-        dest_environ[env] = value
-    for name, value in source_environ.items():
-      if (name.startswith('HTTP_') and
-          not name.startswith(http_runtime_constants.INTERNAL_ENVIRON_PREFIX)):
-        dest_environ[name] = value
-
   def _flush_logs(self, logs):
     """Flushes logs using the LogService API.
 
diff --git a/google/appengine/tools/devappserver2/python/sandbox.py b/google/appengine/tools/devappserver2/python/sandbox.py
index 6b87e47..3064966 100644
--- a/google/appengine/tools/devappserver2/python/sandbox.py
+++ b/google/appengine/tools/devappserver2/python/sandbox.py
@@ -41,12 +41,13 @@
 CODING_MAGIC_COMMENT_RE = re.compile('coding[:=]\s*([-\w.]+)')
 DEFAULT_ENCODING = 'ascii'
 
-_C_MODULES = frozenset(['numpy', 'Crypto', 'lxml', 'PIL'])
+_C_MODULES = frozenset(['cv', 'Crypto', 'lxml', 'numpy', 'PIL'])
 
 NAME_TO_CMODULE_WHITELIST_REGEX = {
+    'cv': re.compile(r'cv(\..*)?$'),
+    'lxml': re.compile(r'lxml(\..*)?$'),
     'numpy': re.compile(r'numpy(\..*)?$'),
     'pycrypto': re.compile(r'Crypto(\..*)?$'),
-    'lxml': re.compile(r'lxml(\..*)?$'),
     'PIL': re.compile(r'(PIL(\..*)?|_imaging|_imagingft|_imagingmath)$'),
     'ssl': re.compile(r'_ssl$'),
 }
@@ -54,7 +55,7 @@
 # Maps App Engine third-party library names to the Python package name for
 # libraries whose names differ from the package names.
 _THIRD_PARTY_LIBRARY_NAME_OVERRIDES = {
-    'pycrypto': 'Crypto'
+    'pycrypto': 'Crypto',
 }
 
 # The location of third-party libraries will be different for the packaged SDK.
diff --git a/google/appengine/tools/devappserver2/runtime_config_pb2.py b/google/appengine/tools/devappserver2/runtime_config_pb2.py
index 28fae40..59a6ec9 100644
--- a/google/appengine/tools/devappserver2/runtime_config_pb2.py
+++ b/google/appengine/tools/devappserver2/runtime_config_pb2.py
@@ -31,7 +31,7 @@
 DESCRIPTOR = _descriptor.FileDescriptor(
   name='apphosting/tools/devappserver2/runtime_config.proto',
   package='apphosting.tools.devappserver2',
-  serialized_pb=_b('\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\x32\n,com.google.appengine.tools.development.proto \x02P\x01')
+  serialized_pb=_b('\n3apphosting/tools/devappserver2/runtime_config.proto\x12\x1e\x61pphosting.tools.devappserver2\"\xaf\x05\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\x12;\n\tvm_config\x18\x13 \x01(\x0b\x32(.apphosting.tools.devappserver2.VMConfig\"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\"%\n\x08VMConfig\x12\x19\n\x11\x64ocker_daemon_url\x18\x01 \x01(\tB2\n,com.google.appengine.tools.development.proto \x02P\x01')
 )
 
 
@@ -170,6 +170,13 @@
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       options=None),
+    _descriptor.FieldDescriptor(
+      name='vm_config', full_name='apphosting.tools.devappserver2.Config.vm_config', index=18,
+      number=19, 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),
   ],
   extensions=[
   ],
@@ -180,7 +187,7 @@
   is_extendable=False,
   extension_ranges=[],
   serialized_start=88,
-  serialized_end=714,
+  serialized_end=775,
 )
 
 
@@ -214,8 +221,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=716,
-  serialized_end=781,
+  serialized_start=777,
+  serialized_end=842,
 )
 
 
@@ -249,8 +256,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=783,
-  serialized_end=843,
+  serialized_start=844,
+  serialized_end=904,
 )
 
 
@@ -305,8 +312,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=845,
-  serialized_end=961,
+  serialized_start=906,
+  serialized_end=1022,
 )
 
 
@@ -340,8 +347,8 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=963,
-  serialized_end=1003,
+  serialized_start=1024,
+  serialized_end=1064,
 )
 
 
@@ -375,8 +382,36 @@
   options=None,
   is_extendable=False,
   extension_ranges=[],
-  serialized_start=1005,
-  serialized_end=1042,
+  serialized_start=1066,
+  serialized_end=1103,
+)
+
+
+_VMCONFIG = _descriptor.Descriptor(
+  name='VMConfig',
+  full_name='apphosting.tools.devappserver2.VMConfig',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='docker_daemon_url', full_name='apphosting.tools.devappserver2.VMConfig.docker_daemon_url', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      options=None),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  options=None,
+  is_extendable=False,
+  extension_ranges=[],
+  serialized_start=1105,
+  serialized_end=1142,
 )
 
 _CONFIG.fields_by_name['libraries'].message_type = _LIBRARY
@@ -384,12 +419,14 @@
 _CONFIG.fields_by_name['php_config'].message_type = _PHPCONFIG
 _CONFIG.fields_by_name['environ'].message_type = _ENVIRON
 _CONFIG.fields_by_name['cloud_sql_config'].message_type = _CLOUDSQL
+_CONFIG.fields_by_name['vm_config'].message_type = _VMCONFIG
 DESCRIPTOR.message_types_by_name['Config'] = _CONFIG
 DESCRIPTOR.message_types_by_name['PhpConfig'] = _PHPCONFIG
 DESCRIPTOR.message_types_by_name['PythonConfig'] = _PYTHONCONFIG
 DESCRIPTOR.message_types_by_name['CloudSQL'] = _CLOUDSQL
 DESCRIPTOR.message_types_by_name['Library'] = _LIBRARY
 DESCRIPTOR.message_types_by_name['Environ'] = _ENVIRON
+DESCRIPTOR.message_types_by_name['VMConfig'] = _VMCONFIG
 
 Config = _reflection.GeneratedProtocolMessageType('Config', (_message.Message,), dict(
   DESCRIPTOR = _CONFIG,
@@ -427,6 +464,12 @@
   # @@protoc_insertion_point(class_scope:apphosting.tools.devappserver2.Environ)
   ))
 
+VMConfig = _reflection.GeneratedProtocolMessageType('VMConfig', (_message.Message,), dict(
+  DESCRIPTOR = _VMCONFIG,
+  __module__ = 'google.appengine.tools.devappserver2.runtime_config_pb2'
+  # @@protoc_insertion_point(class_scope:apphosting.tools.devappserver2.VMConfig)
+  ))
+
 
 DESCRIPTOR.has_options = True
 DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n,com.google.appengine.tools.development.proto \002P\001'))
diff --git a/google/appengine/tools/devappserver2/vm_runtime_proxy.py b/google/appengine/tools/devappserver2/vm_runtime_proxy.py
new file mode 100644
index 0000000..262e75f
--- /dev/null
+++ b/google/appengine/tools/devappserver2/vm_runtime_proxy.py
@@ -0,0 +1,204 @@
+#!/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.
+#
+"""Manages a VM Runtime process running inside of a docker container.
+"""
+
+# TODO: add to third_party
+try:
+  import docker
+except ImportError:
+  docker = None
+
+import logging
+import socket
+
+import google
+
+from google.appengine.tools.devappserver2 import application_configuration
+from google.appengine.tools.devappserver2 import http_proxy
+from google.appengine.tools.devappserver2 import instance
+
+class VMRuntimeProxy(instance.RuntimeProxy):
+  """Manages a VM Runtime process running inside of a docker container"""
+
+  def __init__(self, docker_client, runtime_config_getter,
+               module_configuration):
+    """Initializer for VMRuntimeProxy.
+
+    Args:
+      docker_client: docker.Client object to communicate with Docker daemon.
+      runtime_config_getter: A function that can be called without arguments
+          and returns the runtime_config_pb2.Config containing the configuration
+          for the runtime.
+      module_configuration: An application_configuration.ModuleConfiguration
+          instance respresenting the configuration of the module that owns the
+          runtime.
+    """
+    assert docker, "VM instances are not supported without docker-py installed"
+
+    super(VMRuntimeProxy, self).__init__()
+
+    self._runtime_config_getter = runtime_config_getter
+    self._module_configuration = module_configuration
+    self._docker_client = docker_client
+    self._container_id = None
+    self._proxy = None
+
+  def handle(self, environ, start_response, url_map, match, request_id,
+             request_type):
+    """Serves this request by forwarding it to application instance
+    via HttpProxy.
+
+    Args:
+      environ: An environ dict for the request as defined in PEP-333.
+      start_response: A function with semantics defined in PEP-333.
+      url_map: An appinfo.URLMap instance containing the configuration for the
+          handler matching this request.
+      match: A re.MatchObject containing the result of the matched URL pattern.
+      request_id: A unique string id associated with the request.
+      request_type: The type of the request. See instance.*_REQUEST module
+          constants.
+
+    Yields:
+      A sequence of strings containing the body of the HTTP response.
+    """
+    return self._proxy.handle(environ, start_response, url_map, match,
+                              request_id, request_type)
+
+  def _get_instance_logs(self):
+    # TODO: Handle docker container's logs
+    return ''
+
+  def _instance_died_unexpectedly(self):
+    # TODO: Check if container is still up and running
+    return False
+
+  def start(self):
+    runtime_config = self._runtime_config_getter()
+
+    # api_host set to 'localhost' won't be accessible from a docker container
+    # because container will have it's own 'localhost'.
+    # TODO: this works only when /etc/hosts is configured properly.
+    api_host = socket.gethostbyname(socket.gethostname()) if (
+        runtime_config.api_host == '0.0.0.0') else runtime_config.api_host
+
+    image_id, _ = self._docker_client.build(
+        path=self._module_configuration.application_root,
+        tag='vme.python.%(APP_ID)s.%(MODULE)s.%(VERSION)s' % {
+            'APP_ID': self._module_configuration.application,
+            'MODULE': self._module_configuration.module_name,
+            'VERSION': self._module_configuration.version_id},
+        quiet=False, fileobj=None, nocache=False, rm=False, stream=False)
+
+    # Must be HTTP_PORT from apphosting/ext/vmruntime/vmservice.py
+    # TODO: update apphosting/ext/vmruntime/vmservice.py to use
+    # env var set here.
+    PORT = 8080
+
+    self._container_id = self._docker_client.create_container(
+        image=image_id, hostname=None, user=None, detach=True, stdin_open=False,
+        tty=False, mem_limit=0,
+        ports=[PORT],
+        # TODO: set environment variable for MetadataServer
+        environment={'API_HOST': api_host,
+                     'API_PORT': runtime_config.api_port},
+        dns=None,
+        network_disabled=False, name=None)
+
+    logging.info('Container %s created' % self._container_id)
+
+    self._docker_client.start(
+        self._container_id,
+        # Assigns random available docker port
+        port_bindings={PORT: None})
+
+    logging.info('Container %s started' % self._container_id)
+
+    container_info = self._docker_client.inspect_container(self._container_id)
+    port = int(
+        container_info['NetworkSettings']['Ports']['8080/tcp'][0]['HostPort'])
+
+    self._proxy = http_proxy.HttpProxy(
+        host='localhost', port=port,
+        instance_died_unexpectedly=self._instance_died_unexpectedly,
+        instance_logs_getter=self._get_instance_logs,
+        error_handler_file=application_configuration.get_app_error_file(
+            self._module_configuration))
+
+  def quit(self):
+    """Kills running container and removes it."""
+    self._docker_client.kill(self._container_id)
+    self._docker_client.remove_container(self._container_id, v=False,
+                                         link=False)
+
+class VMRuntimeInstanceFactory(instance.InstanceFactory):
+  """A factory that creates new VM Python runtime Instances."""
+
+  SUPPORTS_INTERACTIVE_REQUESTS = True
+  FILE_CHANGE_INSTANCE_RESTART_POLICY = instance.ALWAYS
+
+  def __init__(self, request_data, runtime_config_getter, module_configuration):
+    """Initializer for VMRuntimeInstanceFactory.
+
+    Args:
+      request_data: A wsgi_request_info.WSGIRequestInfo that will be provided
+          with request information for use by API stubs.
+      runtime_config_getter: A function that can be called without arguments
+          and returns the runtime_config_pb2.Config containing the configuration
+          for the runtime.
+      module_configuration: An application_configuration.ModuleConfiguration
+          instance representing the configuration of the module that owns the
+          runtime.
+    """
+    assert runtime_config_getter().vm_config.HasField('docker_daemon_url'), (
+        'VM runtime requires docker_daemon_url to be specified')
+    super(VMRuntimeInstanceFactory, self).__init__(
+        request_data,
+        8 if runtime_config_getter().threadsafe else 1, 10)
+    self._runtime_config_getter = runtime_config_getter
+    self._module_configuration = module_configuration
+    docker_daemon_url = runtime_config_getter().vm_config.docker_daemon_url
+    self._docker_client = docker.Client(base_url=docker_daemon_url,
+                                        version='1.6',
+                                        timeout=10)
+    if not self._docker_client:
+      logging.error('Couldn\'t connect to docker daemon on %s' %
+                    docker_daemon_url)
+
+  def new_instance(self, instance_id, expect_ready_request=False):
+    """Create and return a new Instance.
+
+    Args:
+      instance_id: A string or integer representing the unique (per module) id
+          of the instance.
+      expect_ready_request: If True then the instance will be sent a special
+          request (i.e. /_ah/warmup or /_ah/start) before it can handle external
+          requests.
+
+    Returns:
+      The newly created instance.Instance.
+    """
+
+    proxy = VMRuntimeProxy(self._docker_client,
+                           self._runtime_config_getter,
+                           self._module_configuration)
+    return instance.Instance(self.request_data,
+                             instance_id,
+                             proxy,
+                             self.max_concurrent_requests,
+                             self.max_background_threads,
+                             expect_ready_request)
diff --git a/google/appengine/tools/endpointscfg.py b/google/appengine/tools/endpointscfg.py
index 1cc8b34..6fa4407 100644
--- a/google/appengine/tools/endpointscfg.py
+++ b/google/appengine/tools/endpointscfg.py
@@ -52,6 +52,8 @@
 try:
   import json
 except ImportError:
+
+
   import simplejson as json
 import os
 import re
@@ -92,9 +94,10 @@
       except (ValueError, TypeError, KeyError):
         pass
     if error_details:
+      error_details_str = ', '.join(error_details)
       error_message = ('HTTP %s (%s) error when communicating with URL: %s.  '
                        'Details: %s' % (http_error.code, http_error.reason,
-                                        http_error.filename, error_details))
+                                        http_error.filename, error_details_str))
     else:
       error_message = ('HTTP %s (%s) error when communicating with URL: %s.' %
                        (http_error.code, http_error.reason,
@@ -119,11 +122,16 @@
     Args:
       message: original error message that will be printed to stderr
     """
-    options = ', '.join([repr(command) for command in _VISIBLE_COMMANDS])
+
+
+
+
+    subcommands_quoted = ', '.join(
+        [repr(command) for command in _VISIBLE_COMMANDS])
     subcommands = ', '.join(_VISIBLE_COMMANDS)
     message = re.sub(
         r'(argument {%s}: invalid choice: .*) \(choose from (.*)\)$'
-        % subcommands, r'\1 (choose from %s)' % options, message)
+        % subcommands, r'\1 (choose from %s)' % subcommands_quoted, message)
     super(_EndpointsParser, self).error(message)
 
 
@@ -144,17 +152,17 @@
   return path
 
 
-def GenApiConfig(service_class_names, generator=None, hostname=None,
-                 application_path=None):
+def GenApiConfig(service_class_names, config_string_generator=None,
+                 hostname=None, application_path=None):
   """Write an API configuration for endpoints annotated ProtoRPC services.
 
   Args:
     service_class_names: A list of fully qualified ProtoRPC service classes.
-    generator: An generator object that produces API config strings using its
-      pretty_print_config_to_json method.
+    config_string_generator: A generator object that produces API config strings
+      using its pretty_print_config_to_json method.
     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.
+      this value is the fallback.
     application_path: A string with the path to the AppEngine application.
 
   Raises:
@@ -174,12 +182,11 @@
     module_name, base_service_class_name = service_class_name.rsplit('.', 1)
     module = __import__(module_name, fromlist=base_service_class_name)
     service = getattr(module, base_service_class_name)
-    if not (isinstance(service, type) and issubclass(service, remote.Service)):
+    if not isinstance(service, type) or not issubclass(service, remote.Service):
       raise TypeError('%s is not a ProtoRPC service' % service_class_name)
 
-    services = api_service_map.setdefault((service.api_info.name,
-                                           service.api_info.version),
-                                          [])
+    services = api_service_map.setdefault(
+        (service.api_info.name, service.api_info.version), [])
     services.append(service)
 
 
@@ -187,15 +194,18 @@
   app_yaml_hostname = _GetAppYamlHostname(application_path)
 
   service_map = collections.OrderedDict()
-  generator = generator or api_config.ApiConfigGenerator()
+  config_string_generator = (
+      config_string_generator or api_config.ApiConfigGenerator())
   for api_info, services in api_service_map.iteritems():
+    assert len(services) > 0, 'An API must have at least one ProtoRPC service'
 
 
     hostname = services[0].api_info.hostname or hostname or app_yaml_hostname
 
 
-    service_map['%s-%s' % api_info] = generator.pretty_print_config_to_json(
-        services, hostname=hostname)
+    service_map['%s-%s' % api_info] = (
+        config_string_generator.pretty_print_config_to_json(
+            services, hostname=hostname))
 
   return service_map
 
@@ -394,8 +404,7 @@
                              application_path=args.application)
 
   for api_name_version, config in service_configs.iteritems():
-    api_name = api_name_version + '.api'
-    _WriteFile(args.output, api_name, config)
+    _WriteFile(args.output, api_name_version + '.api', config)
 
 
 def _GetClientLibCallback(args, client_func=_GetClientLib):
@@ -542,8 +551,7 @@
     sys.path.insert(0, os.path.abspath(application_path))
 
   args.callback(args)
-  return 0
 
 
 if __name__ == '__main__':
-  sys.exit(main(sys.argv))
+  main(sys.argv)
diff --git a/google/appengine/tools/dev_appserver.py b/google/appengine/tools/old_dev_appserver.py
similarity index 100%
rename from google/appengine/tools/dev_appserver.py
rename to google/appengine/tools/old_dev_appserver.py
diff --git a/google/appengine/tools/remote_api_shell.py b/google/appengine/tools/remote_api_shell.py
index f6ea7f8..f6d9ec2 100644
--- a/google/appengine/tools/remote_api_shell.py
+++ b/google/appengine/tools/remote_api_shell.py
@@ -21,7 +21,7 @@
 """An interactive python shell that uses remote_api.
 
 Usage:
-  %prog [-s HOSTNAME] [-p PATH] [APPID]
+  %prog [-s HOSTNAME] [-p PATH] [--secure] [APPID]
 
 If the -s HOSTNAME flag is not specified, the APPID must be specified.
 """
diff --git a/google/appengine/tools/sdk_update_checker.py b/google/appengine/tools/sdk_update_checker.py
index 975267f..b361435 100644
--- a/google/appengine/tools/sdk_update_checker.py
+++ b/google/appengine/tools/sdk_update_checker.py
@@ -20,6 +20,7 @@
 import logging
 import os
 import socket
+import ssl
 import sys
 import time
 import urllib2
@@ -253,7 +254,7 @@
             timestamp=version['timestamp'],
             api_versions=version['api_versions'],
             runtime=runtime))
-    except (urllib2.URLError, socket.error), e:
+    except (urllib2.URLError, socket.error, ssl.SSLError), e:
       logging.info('Update check failed: %s', e)
       return
 
diff --git a/google/appengine/tools/yaml_translator.py b/google/appengine/tools/yaml_translator.py
index e85ea6e..10509bd 100644
--- a/google/appengine/tools/yaml_translator.py
+++ b/google/appengine/tools/yaml_translator.py
@@ -22,7 +22,6 @@
 """
 
 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
@@ -32,10 +31,9 @@
 
 
 def TranslateXmlToYaml(app_engine_web_xml_str,
-                       backends_xml_str,
                        web_xml_str,
                        static_files,
-                       api_version):
+                       api_version='1.0'):
   """Does xml-string to yaml-string translation, given each separate file text.
 
   Processes each xml string into an object representing the xml,
@@ -43,7 +41,6 @@
 
   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
@@ -55,13 +52,11 @@
     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)
+      app_engine_web_xml, web_xml, static_files, api_version)
   return translator.GetYaml()
 
 
@@ -76,18 +71,15 @@
   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
@@ -106,7 +98,6 @@
     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'
@@ -258,26 +249,6 @@
 
     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,
diff --git a/google/net/proto2/python/public/descriptor.py b/google/net/proto2/python/public/descriptor.py
index dca89d0..d9a0cff 100644
--- a/google/net/proto2/python/public/descriptor.py
+++ b/google/net/proto2/python/public/descriptor.py
@@ -454,12 +454,17 @@
     if api_implementation.Type() == 'cpp':
       if is_extension:
         if api_implementation.Version() == 2:
-          self._cdescriptor = _message.GetExtensionDescriptor(full_name)
+
+          self._cdescriptor = (
+              _message.Message._GetExtensionDescriptor(full_name))
+
         else:
           self._cdescriptor = cpp_message.GetExtensionDescriptor(full_name)
       else:
         if api_implementation.Version() == 2:
-          self._cdescriptor = _message.GetFieldDescriptor(full_name)
+
+          self._cdescriptor = _message.Message._GetFieldDescriptor(full_name)
+
         else:
           self._cdescriptor = cpp_message.GetFieldDescriptor(full_name)
     else:
@@ -676,7 +681,9 @@
     if (api_implementation.Type() == 'cpp' and
         self.serialized_pb is not None):
       if api_implementation.Version() == 2:
-        _message.BuildFile(self.serialized_pb)
+
+        _message.Message._BuildFile(self.serialized_pb)
+
       else:
         cpp_message.BuildFile(self.serialized_pb)
 
@@ -737,7 +744,9 @@
       file_descriptor_proto.name = proto_name + '.proto'
 
     if api_implementation.Version() == 2:
-      _message.BuildFile(file_descriptor_proto.SerializeToString())
+
+      _message.Message._BuildFile(file_descriptor_proto.SerializeToString())
+
     else:
       cpp_message.BuildFile(file_descriptor_proto.SerializeToString())
 
diff --git a/php/sdk/google/appengine/api/log/LogService.php b/php/sdk/google/appengine/api/log/LogService.php
index c1d2c7e..4beffe8 100644
--- a/php/sdk/google/appengine/api/log/LogService.php
+++ b/php/sdk/google/appengine/api/log/LogService.php
@@ -389,7 +389,7 @@
     $log_level = self::getAppEngineLogLevel($priority);
     self::log($log_level, $message);
     if (function_exists('_gae_syslog')) {
-      _gae_syslog($log_level);
+      _gae_syslog($log_level, $message);
     }
   }
 }
diff --git a/php/sdk/google/appengine/datastore/datastore_v3_pb.php b/php/sdk/google/appengine/datastore/datastore_v3_pb.php
index 7b8c494..625984b 100644
--- a/php/sdk/google/appengine/datastore/datastore_v3_pb.php
+++ b/php/sdk/google/appengine/datastore/datastore_v3_pb.php
@@ -26,47 +26,78 @@
 }
 namespace google\appengine_datastore_v3 {
   class InternalHeader extends \google\net\ProtocolMessage {
-    public function getQos() {
-      if (!isset($this->qos)) {
+    public function getRequestingAppId() {
+      if (!isset($this->requesting_app_id)) {
         return '';
       }
-      return $this->qos;
+      return $this->requesting_app_id;
     }
-    public function setQos($val) {
-      $this->qos = $val;
+    public function setRequestingAppId($val) {
+      $this->requesting_app_id = $val;
       return $this;
     }
-    public function clearQos() {
-      unset($this->qos);
+    public function clearRequestingAppId() {
+      unset($this->requesting_app_id);
       return $this;
     }
-    public function hasQos() {
-      return isset($this->qos);
+    public function hasRequestingAppId() {
+      return isset($this->requesting_app_id);
+    }
+    public function getApiSettings() {
+      if (!isset($this->api_settings)) {
+        return '';
+      }
+      return $this->api_settings;
+    }
+    public function setApiSettings($val) {
+      $this->api_settings = $val;
+      return $this;
+    }
+    public function clearApiSettings() {
+      unset($this->api_settings);
+      return $this;
+    }
+    public function hasApiSettings() {
+      return isset($this->api_settings);
     }
     public function clear() {
-      $this->clearQos();
+      $this->clearRequestingAppId();
+      $this->clearApiSettings();
     }
     public function byteSizePartial() {
       $res = 0;
-      if (isset($this->qos)) {
+      if (isset($this->requesting_app_id)) {
         $res += 1;
-        $res += $this->lengthString(strlen($this->qos));
+        $res += $this->lengthString(strlen($this->requesting_app_id));
+      }
+      if (isset($this->api_settings)) {
+        $res += 1;
+        $res += $this->lengthString(strlen($this->api_settings));
       }
       return $res;
     }
     public function outputPartial($out) {
-      if (isset($this->qos)) {
-        $out->putVarInt32(10);
-        $out->putPrefixedString($this->qos);
+      if (isset($this->requesting_app_id)) {
+        $out->putVarInt32(18);
+        $out->putPrefixedString($this->requesting_app_id);
+      }
+      if (isset($this->api_settings)) {
+        $out->putVarInt32(26);
+        $out->putPrefixedString($this->api_settings);
       }
     }
     public function tryMerge($d) {
       while($d->avail() > 0) {
         $tt = $d->getVarInt32();
         switch ($tt) {
-          case 10:
+          case 18:
             $length = $d->getVarInt32();
-            $this->setQos(substr($d->buffer(), $d->pos(), $length));
+            $this->setRequestingAppId(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
+          case 26:
+            $length = $d->getVarInt32();
+            $this->setApiSettings(substr($d->buffer(), $d->pos(), $length));
             $d->skip($length);
             break;
           case 0:
@@ -82,20 +113,28 @@
     }
     public function mergeFrom($x) {
       if ($x === $this) { throw new \IllegalArgumentException('Cannot copy message to itself'); }
-      if ($x->hasQos()) {
-        $this->setQos($x->getQos());
+      if ($x->hasRequestingAppId()) {
+        $this->setRequestingAppId($x->getRequestingAppId());
+      }
+      if ($x->hasApiSettings()) {
+        $this->setApiSettings($x->getApiSettings());
       }
     }
     public function equals($x) {
       if ($x === $this) { return true; }
-      if (isset($this->qos) !== isset($x->qos)) return false;
-      if (isset($this->qos) && $this->qos !== $x->qos) return false;
+      if (isset($this->requesting_app_id) !== isset($x->requesting_app_id)) return false;
+      if (isset($this->requesting_app_id) && $this->requesting_app_id !== $x->requesting_app_id) return false;
+      if (isset($this->api_settings) !== isset($x->api_settings)) return false;
+      if (isset($this->api_settings) && $this->api_settings !== $x->api_settings) return false;
       return true;
     }
     public function shortDebugString($prefix = "") {
       $res = '';
-      if (isset($this->qos)) {
-        $res .= $prefix . "qos: " . $this->debugFormatString($this->qos) . "\n";
+      if (isset($this->requesting_app_id)) {
+        $res .= $prefix . "requesting_app_id: " . $this->debugFormatString($this->requesting_app_id) . "\n";
+      }
+      if (isset($this->api_settings)) {
+        $res .= $prefix . "api_settings: " . $this->debugFormatString($this->api_settings) . "\n";
       }
       return $res;
     }
@@ -2658,6 +2697,23 @@
     public function hasDistinctInfixSize() {
       return isset($this->distinct_infix_size);
     }
+    public function getPlanLabel() {
+      if (!isset($this->plan_label)) {
+        return '';
+      }
+      return $this->plan_label;
+    }
+    public function setPlanLabel($val) {
+      $this->plan_label = $val;
+      return $this;
+    }
+    public function clearPlanLabel() {
+      unset($this->plan_label);
+      return $this;
+    }
+    public function hasPlanLabel() {
+      return isset($this->plan_label);
+    }
     public function clear() {
       $this->clearPrimaryScan();
       $this->clearMergeJoinScan();
@@ -2668,6 +2724,7 @@
       $this->clearIndexDef();
       $this->clearPropertyName();
       $this->clearDistinctInfixSize();
+      $this->clearPlanLabel();
     }
     public function byteSizePartial() {
       $res = 0;
@@ -2708,6 +2765,10 @@
         $res += 2;
         $res += $this->lengthVarInt64($this->distinct_infix_size);
       }
+      if (isset($this->plan_label)) {
+        $res += 2;
+        $res += $this->lengthString(strlen($this->plan_label));
+      }
       return $res;
     }
     public function outputPartial($out) {
@@ -2753,6 +2814,10 @@
         $out->putVarInt32(200);
         $out->putVarInt32($this->distinct_infix_size);
       }
+      if (isset($this->plan_label)) {
+        $out->putVarInt32(210);
+        $out->putPrefixedString($this->plan_label);
+      }
     }
     public function tryMerge($d) {
       while($d->avail() > 0) {
@@ -2790,6 +2855,11 @@
           case 200:
             $this->setDistinctInfixSize($d->getVarInt32());
             break;
+          case 210:
+            $length = $d->getVarInt32();
+            $this->setPlanLabel(substr($d->buffer(), $d->pos(), $length));
+            $d->skip($length);
+            break;
           case 0:
             throw new \google\net\ProtocolBufferDecodeError();
             break;
@@ -2837,6 +2907,9 @@
       if ($x->hasDistinctInfixSize()) {
         $this->setDistinctInfixSize($x->getDistinctInfixSize());
       }
+      if ($x->hasPlanLabel()) {
+        $this->setPlanLabel($x->getPlanLabel());
+      }
     }
     public function equals($x) {
       if ($x === $this) { return true; }
@@ -2862,6 +2935,8 @@
       }
       if (isset($this->distinct_infix_size) !== isset($x->distinct_infix_size)) return false;
       if (isset($this->distinct_infix_size) && !$this->integerEquals($this->distinct_infix_size, $x->distinct_infix_size)) return false;
+      if (isset($this->plan_label) !== isset($x->plan_label)) return false;
+      if (isset($this->plan_label) && $this->plan_label !== $x->plan_label) return false;
       return true;
     }
     public function shortDebugString($prefix = "") {
@@ -2893,6 +2968,9 @@
       if (isset($this->distinct_infix_size)) {
         $res .= $prefix . "distinct_infix_size: " . $this->debugFormatInt32($this->distinct_infix_size) . "\n";
       }
+      if (isset($this->plan_label)) {
+        $res .= $prefix . "plan_label: " . $this->debugFormatString($this->plan_label) . "\n";
+      }
       return $res;
     }
   }
diff --git a/php/sdk/google/appengine/datastore/datastore_v4_pb.php b/php/sdk/google/appengine/datastore/datastore_v4_pb.php
index 303f391..e05d911 100644
--- a/php/sdk/google/appengine/datastore/datastore_v4_pb.php
+++ b/php/sdk/google/appengine/datastore/datastore_v4_pb.php
@@ -2232,7 +2232,6 @@
     const UPDATE = 2;
     const UPSERT = 3;
     const DELETE = 4;
-    const INSERT_WITH_AUTO_ID = 99;
   }
 }
 namespace google\appengine\datastore\v4 {
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 d566f2e..9941518 100644
--- a/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageClient.php
+++ b/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageClient.php
@@ -28,7 +28,9 @@
 use google\appengine\runtime\ApiProxy;
 use google\appengine\runtime\ApplicationError;
 use google\appengine\URLFetchRequest\RequestMethod;
+use google\appengine\URLFetchServiceError\ErrorCode;
 use google\appengine\util\ArrayUtil;
+use google\appengine\util\StringUtil;
 
 /**
  * CloudStorageClient provides default fail implementations for all of the
@@ -37,6 +39,23 @@
  * perform.
  */
 abstract class CloudStorageClient {
+  /**
+   * Headers that may be controlled by the user through the stream context.
+   */
+  protected static $METADATA_HEADERS = [
+    'Cache-Control',
+    'Content-Disposition',
+    'Content-Encoding',
+    'Content-Language',
+    'Content-Type',
+    // x-goog-meta-* handled separately.
+  ];
+
+  /**
+   * Prefix for all metadata headers used when parsing and rendering.
+   */
+  const METADATA_HEADER_PREFIX = 'x-goog-meta-';
+
   // The default chunk size that we will read from the file. This value should
   // remain smaller than the maximum object size valid for memcache writes so
   // we can cache the reads.
@@ -132,6 +151,11 @@
                                          HttpResponse::SERVICE_UNAVAILABLE,
                                          HttpResponse::GATEWAY_TIMEOUT];
 
+  protected static $retry_exception_codes = [
+      ErrorCode::DEADLINE_EXCEEDED,
+      ErrorCode::FETCH_ERROR,
+      ErrorCode::INTERNAL_TRANSIENT_ERROR];
+
   // Values that are allowed to be supplied as ACLs when writing objects.
   protected static $valid_acl_values = ["private",
                                         "public-read",
@@ -359,7 +383,6 @@
    * @return The value of the header if found, false otherwise.
    */
   protected function getHeaderValue($header_name, $headers) {
-    // Could be more than one header, in which case we keep an array.
     foreach($headers as $key => $value) {
       if (strcasecmp($key, $header_name) === 0) {
         return $value;
@@ -392,11 +415,21 @@
       try {
         ApiProxy::makeSyncCall('urlfetch', 'Fetch', $req, $resp);
       } catch (ApplicationError $e) {
-        syslog(LOG_ERR,
-               sprintf("Call to URLFetch failed with application error %d.",
-                       $e->getApplicationError()));
-        return false;
+        if (in_array($e->getApplicationError(), self::$retry_exception_codes)) {
+          // We need to set a plausible value in the URLFetchResponse proto in
+          // case the retry loop falls through - this will also cause a retry
+          // if one is available.
+          $resp->setStatusCode(HttpResponse::GATEWAY_TIMEOUT);
+        } else {
+          syslog(LOG_ERR,
+                 sprintf("Call to URLFetch failed with application error %d " .
+                         "for url %s.",
+                         $e->getApplicationError(),
+                         $url));
+          return false;
+        }
       }
+
       $status_code = $resp->getStatusCode();
 
       if ($num_retries < $this->context_options['max_retries'] &&
@@ -451,6 +484,30 @@
   }
 
   /**
+   * Extract metadata from HTTP response headers.
+   *
+   * Finds all headers that begin with METADATA_HEADER_PREFIX (x-goog-meta-),
+   * strips off the prefix, and creates an associative array.
+   *
+   * @param array $headers
+   *   Associative array of HTTP headers.
+   * @return array
+   *   Array of parsed metadata headers.
+   */
+  protected static function extractMetaData(array $headers) {
+    $metadata = [];
+    foreach($headers as $key => $value) {
+      if (StringUtil::startsWith(strtolower($key),
+                                 static::METADATA_HEADER_PREFIX)) {
+        $metadata_key = substr($key, strlen(static::METADATA_HEADER_PREFIX));
+        $metadata[$metadata_key] = $value;
+      }
+    }
+
+    return $metadata;
+  }
+
+  /**
    * Given an xml based error response from Cloud Storage, try and extract the
    * error code and error message according to the schema described at
    * https://developers.google.com/storage/docs/reference-status
diff --git a/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageReadClient.php b/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageReadClient.php
index 4535987..c9e019e 100644
--- a/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageReadClient.php
+++ b/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageReadClient.php
@@ -23,14 +23,10 @@
 
 namespace google\appengine\ext\cloud_storage_streams;
 
-use google\appengine\util\StringUtil;
-
 /**
  * Google Cloud Storage Client for reading objects.
  */
 final class CloudStorageReadClient extends CloudStorageClient {
-  const METADATA_HEADER_PREFIX = 'x-goog-meta-';
-
   // Buffer for storing data.
   private $read_buffer;
 
@@ -356,21 +352,5 @@
 
     return true;
   }
-
-  /**
-   * Extract metadata from HTTP response headers.
-   */
-  private static function extractMetaData($headers) {
-    $metadata = [];
-    foreach($headers as $key => $value) {
-      if (StringUtil::startsWith(strtolower($key),
-                                 self::METADATA_HEADER_PREFIX)) {
-        $metadata_key = substr($key, strlen(self::METADATA_HEADER_PREFIX));
-        $metadata[$metadata_key] = $value;
-      }
-    }
-
-    return $metadata;
-  }
 }
 
diff --git a/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageRenameClient.php b/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageRenameClient.php
index 6256152..bc55553 100644
--- a/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageRenameClient.php
+++ b/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageRenameClient.php
@@ -78,26 +78,61 @@
       return false;
     }
 
-    $from_etag = $this->getHeaderValue('ETag', $http_response['headers']);
-    $content_type = $this->getHeaderValue('Content-Type',
-                                          $http_response['headers']);
-
     $copy_headers = [
-        'x-goog-copy-source' =>
-            sprintf("/%s%s", $this->from_bucket, $this->from_object),
-        'x-goog-copy-source-if-match' => $from_etag,
+      'x-goog-copy-source' =>
+        sprintf("/%s%s", $this->from_bucket, $this->from_object),
+      'x-goog-copy-source-if-match' =>
+        $this->getHeaderValue('ETag', $http_response['headers']),
     ];
 
-    if (array_key_exists('Content-Type', $this->context_options)) {
-      $copy_headers['content-type'] = $this->context_options['Content-Type'];
-    } else {
-      $copy_headers['content-type'] = $content_type;
+    // TODO: b/13132830: Remove once feature releases.
+    if (!ini_get('google_app_engine.enable_additional_cloud_storage_headers')) {
+      foreach (static::$METADATA_HEADERS as $key) {
+        // Leave Content-Type since it has been supported.
+        if ($key != 'Content-Type') {
+          unset($this->context_options[$key]);
+        }
+      }
     }
 
-    if (array_key_exists('metadata', $this->context_options)) {
+    // Check if any metadata context options have been set and only copy values
+    // below if one option needs to be changed.
+    $is_meta = isset($this->context_options['metadata']);
+    foreach (static::$METADATA_HEADERS as $key) {
+      // Stop after first meta option found.
+      $is_meta = $is_meta ?: isset($this->context_options[$key]);
+      if ($is_meta) {
+        break;
+      }
+    }
+
+    // Metadata-directive applies to headers outside of x-goog-meta-* like
+    // Content-Type. If any metadata changes are included in context then all
+    // other meta data must be filled in since the metadata-directive will be
+    // set to REPLACE and non-passed in values should remain the same.
+    if ($is_meta) {
       $copy_headers['x-goog-metadata-directive'] = 'REPLACE';
-      foreach ($this->context_options['metadata'] as $key => $val) {
-        $copy_headers['x-goog-meta-' . $key] = $val;
+
+      // Copy all meta data fields to preserve values when using REPLACE
+      // directive. If a value exists in context_options it is given preference.
+      foreach (static::$METADATA_HEADERS as $key) {
+        if (isset($this->context_options[$key])) {
+          $copy_headers[$key] = $this->context_options[$key];
+        } else {
+          $value = $this->getHeaderValue($key, $http_response['headers']);
+          if ($value !== null) {
+            $copy_headers[$key] = $value;
+          }
+        }
+      }
+
+      // Special case since metadata option converts to multiple headers, this
+      // also handles copying over previous values if no new ones speicified.
+      $metadata = isset($this->context_options['metadata']) ?
+        $this->context_options['metadata'] :
+        static::extractMetaData($http_response['headers']);
+      foreach ($metadata as $key => $value) {
+        $copy_headers['x-goog-meta-' . $key] = $value;
       }
     } else {
       $copy_headers['x-goog-metadata-directive'] = 'COPY';
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 d6a1303..1615099 100644
--- a/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageStreamWrapper.php
+++ b/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageStreamWrapper.php
@@ -364,6 +364,7 @@
     if (!isset($allowed_buckets)) {
       $allowed_buckets = explode(',', GAE_INCLUDE_GS_BUCKETS);
       $allowed_buckets = array_map('trim', $allowed_buckets);
+      $allowed_bukcets = array_filter($allowed_buckets);
     }
 
     return $allowed_buckets;
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 1ba6cc9..b81dfa3 100644
--- a/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageStreamWrapperTest.php
+++ b/php/sdk/google/appengine/ext/cloud_storage_streams/CloudStorageStreamWrapperTest.php
@@ -57,13 +57,6 @@
 
 namespace google\appengine\ext\cloud_storage_streams {
 
-require_once 'google/appengine/api/app_identity/app_identity_service_pb.php';
-require_once 'google/appengine/api/app_identity/AppIdentityService.php';
-require_once 'google/appengine/api/urlfetch_service_pb.php';
-require_once 'google/appengine/ext/cloud_storage_streams/CloudStorageClient.php';
-require_once 'google/appengine/ext/cloud_storage_streams/CloudStorageReadClient.php';
-require_once 'google/appengine/ext/cloud_storage_streams/CloudStorageStreamWrapper.php';
-require_once 'google/appengine/ext/cloud_storage_streams/CloudStorageWriteClient.php';
 require_once 'google/appengine/testing/ApiProxyTestBase.php';
 
 use google\appengine\testing\ApiProxyTestBase;
@@ -72,6 +65,8 @@
 use google\appengine\ext\cloud_storage_streams\CloudStorageWriteClient;
 use google\appengine\ext\cloud_storage_streams\HttpResponse;
 use google\appengine\URLFetchRequest\RequestMethod;
+use google\appengine\URLFetchServiceError\ErrorCode;
+use google\appengine\runtime\ApplicationError;
 
 class CloudStorageStreamWrapperTest extends ApiProxyTestBase {
 
@@ -239,6 +234,51 @@
         "x-goog-api-version" => 2,
     ];
 
+    // The first request will fail urlfetch deadline exceeded exception
+    $failure_response = new ApplicationError(ErrorCode::DEADLINE_EXCEEDED);
+
+    $this->expectHttpRequest($exected_url,
+                             RequestMethod::GET,
+                             $request_headers,
+                             null,
+                             $failure_response);
+
+    // The second request will succeed.
+    $response_headers = [
+        "ETag" => "deadbeef",
+        "Content-Type" => "text/plain",
+        "Last-Modified" => "Mon, 02 Jul 2012 01:41:01 GMT",
+    ];
+    $response = $this->createSuccessfulGetHttpResponse(
+         $response_headers,
+         $body,
+         0,
+         CloudStorageReadClient::DEFAULT_READ_SIZE,
+         null);
+    $this->expectHttpRequest($exected_url,
+                             RequestMethod::GET,
+                             $request_headers,
+                             null,
+                             $response);
+
+    $data = file_get_contents("gs://bucket/object_name.png");
+    $this->assertEquals($body, $data);
+    $this->apiProxyMock->verify();
+  }
+
+  public function testReadObjectUrlFetchExceptionThenSuccess() {
+    $body = "Hello from PHP";
+
+    $this->expectGetAccessTokenRequest(CloudStorageClient::READ_SCOPE);
+    $exected_url = self::makeCloudStorageObjectUrl("bucket",
+                                                   "/object_name.png");
+    $request_headers = [
+        "Authorization" => "OAuth foo token",
+        "Range" => sprintf("bytes=0-%d",
+                           CloudStorageReadClient::DEFAULT_READ_SIZE-1),
+        "x-goog-api-version" => 2,
+    ];
+
     // The first request will fail with a 500 error, which can be retried.
     $failure_response = [
         "status_code" => 500,
@@ -826,7 +866,6 @@
         "Authorization" => "OAuth foo token",
         "x-goog-copy-source" => '/bucket/object.png',
         "x-goog-copy-source-if-match" => 'abcdef',
-        "content-type" => 'text/plain',
         "x-goog-metadata-directive" => "COPY",
         "x-goog-api-version" => 2,
     ];
@@ -886,6 +925,12 @@
         'headers' => [
             'Content-Length' => 37337,
             'ETag' => 'abcdef',
+            // Ensure the pre-existing headers are preserved.
+            'Cache-Control' => 'public, max-age=6000',
+            'Content-Disposition' => 'attachment; filename=object.png',
+            'Content-Encoding' => 'text/plain',
+            'Content-Language' => 'en',
+            // Ensure context overrides original.
             'Content-Type' => 'text/plain',
         ],
     ];
@@ -902,8 +947,12 @@
         "Authorization" => "OAuth foo token",
         "x-goog-copy-source" => "/bucket/object.png",
         "x-goog-copy-source-if-match" => "abcdef",
-        "content-type" => "image/png",
         "x-goog-metadata-directive" => "REPLACE",
+        "Cache-Control" => "public, max-age=6000",
+        "Content-Disposition" => "attachment; filename=object.png",
+        "Content-Encoding" => "text/plain",
+        "Content-Language" => "en",
+        "Content-Type" => "image/png",
         "x-goog-meta-foo" => "bar",
         "x-goog-acl" => "public-read-write",
         "x-goog-api-version" => 2,
@@ -945,6 +994,93 @@
     $this->apiProxyMock->verify();
   }
 
+  public function testRenameObjectWithContextAllMetaSuccess() {
+    $this->expectGetAccessTokenRequest(CloudStorageClient::WRITE_SCOPE);
+
+    // First there is a stat.
+    $request_headers = $this->getStandardRequestHeaders();
+    $response = [
+        'status_code' => 200,
+        'headers' => [
+            'Content-Length' => 37337,
+            'ETag' => 'abcdef',
+            // Ensure context overrides original values.
+            'Cache-Control' => 'public, max-age=6000',
+            'Content-Disposition' => 'attachment; filename=object.png',
+            'Content-Encoding' => 'text/plain',
+            'Content-Language' => 'en',
+            'Content-Type' => 'text/plain',
+        ],
+    ];
+
+    $expected_url = $this->makeCloudStorageObjectUrl();
+    $this->expectHttpRequest($expected_url,
+                             RequestMethod::HEAD,
+                             $request_headers,
+                             null,
+                             $response);
+
+    // Then there is a copy with new context.
+    $request_headers = [
+        "Authorization" => "OAuth foo token",
+        "x-goog-copy-source" => "/bucket/object.png",
+        "x-goog-copy-source-if-match" => "abcdef",
+        "x-goog-metadata-directive" => "REPLACE",
+        // All meta heads have had a 2 appended to check that context overrides.
+        "Cache-Control" => "public, max-age=6002",
+        "Content-Disposition" => "attachment; filename=object.png2",
+        "Content-Encoding" => "text/plain2",
+        "Content-Language" => "en2",
+        "Content-Type" => "image/png2",
+        "x-goog-meta-foo" => "bar",
+        "x-goog-acl" => "public-read-write",
+        "x-goog-api-version" => 2,
+    ];
+    $response = [
+        'status_code' => 200,
+        'headers' => [
+        ]
+    ];
+    $expected_url = $this->makeCloudStorageObjectUrl("to_bucket", "/to.png");
+    $this->expectHttpRequest($expected_url,
+                             RequestMethod::PUT,
+                             $request_headers,
+                             null,
+                             $response);
+
+    // Then we unlink the original.
+    $request_headers = $this->getStandardRequestHeaders();
+    $response = [
+        'status_code' => 204,
+        'headers' => [
+        ],
+    ];
+    $expected_url = $this->makeCloudStorageObjectUrl();
+    $this->expectHttpRequest($expected_url,
+                             RequestMethod::DELETE,
+                             $request_headers,
+                             null,
+                             $response);
+
+    $from = "gs://bucket/object.png";
+    $to = "gs://to_bucket/to.png";
+    $ctx = stream_context_create([
+      "gs" => [
+        "acl" => "public-read-write",
+        "metadata" => ["foo"=> "bar"],
+        // Metadata heads to override.
+        "Cache-Control" => "public, max-age=6002",
+        "Content-Disposition" => "attachment; filename=object.png2",
+        "Content-Encoding" => "text/plain2",
+        "Content-Language" => "en2",
+        "Content-Type" => "image/png2",
+      ],
+    ]);
+
+    $this->assertTrue(rename($from, $to, $ctx));
+    $this->apiProxyMock->verify();
+  }
+
   public function testRenameObjectFromObjectNotFound() {
     $this->expectGetAccessTokenRequest(CloudStorageClient::WRITE_SCOPE);
 
@@ -1001,7 +1137,6 @@
         "Authorization" => "OAuth foo token",
         "x-goog-copy-source" => '/bucket/object.png',
         "x-goog-copy-source-if-match" => 'abcdef',
-        "content-type" => 'text/plain',
         "x-goog-metadata-directive" => "COPY",
         "x-goog-api-version" => 2,
     ];
@@ -1055,7 +1190,6 @@
         "Authorization" => "OAuth foo token",
         "x-goog-copy-source" => '/bucket/object.png',
         "x-goog-copy-source-if-match" => 'abcdef',
-        "content-type" => 'text/plain',
         "x-goog-metadata-directive" => "COPY",
         "x-goog-api-version" => 2,
     ];
@@ -1107,14 +1241,28 @@
     $this->writeObjectSuccessWithMetadata("Goodbye To PHP.", $metadata);
   }
 
-  private function writeObjectSuccessWithMetadata($data, $metadata = NULL) {
+  public function testWriteObjectWithAllMetadataHeaders() {
+    $metadata = ['foo' => 'far', 'bar' => 'boo'];
+    $headers = [
+      'Cache-Control' => 'public, max-age=6000',
+      'Content-Disposition' => 'attachment; filename=object.png',
+      'Content-Encoding' => 'text/plain',
+      'Content-Language' => 'en',
+    ];