text: #!/usr/bin/env python
  #
  # Copyright 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  #
  """Contains logic for writing handlers to app.yaml.
  
  Information about handlers comes from static file paths, appengine-web.xml,
  and web.xml. This information is packaged into Handler objects which specify
  paths and properties about how they are handled.
  
  In this module:
    GenerateYamlHandlers: Returns Yaml string with handlers.
    HandlerGenerator: Ancestor class for creating handler lists.
    DynamicHandlerGenerator: Creates handlers from web-xml servlet and filter
      mappings.
    StaticHandlerGenerator: Creates handlers from static file includes and
      static files.
  """
  
  from google.appengine.tools import handler
  from google.appengine.tools.app_engine_config_exception import AppEngineConfigException
  
  API_ENDPOINT_REGEX = '/_ah/spi/*'
  MAX_HANDLERS = 100
  
  
  def GenerateYamlHandlersList(app_engine_web_xml, web_xml, static_files):
    """Produces a list of Yaml strings for dynamic and static handlers."""
    static_handler_generator = StaticHandlerGenerator(
        app_engine_web_xml, web_xml, static_files)
    dynamic_handler_generator = DynamicHandlerGenerator(
        app_engine_web_xml, web_xml)
  
    if (len(static_handler_generator.GenerateOrderedHandlerList()) +
        len(dynamic_handler_generator.GenerateOrderedHandlerList())
        > MAX_HANDLERS):
  
  
  
  
      dynamic_handler_generator.fall_through = True
      dynamic_handler_generator.welcome_properties = {}
  
    yaml_statements = ['handlers:']
    if static_files:
      static_handler_generator = StaticHandlerGenerator(
          app_engine_web_xml, web_xml, static_files)
      yaml_statements += static_handler_generator.GetHandlerYaml()
    yaml_statements += dynamic_handler_generator.GetHandlerYaml()
  
    return yaml_statements
  
  
  def GenerateYamlHandlers(app_engine_web_xml, web_xml, static_files):
    """Produces Yaml string writable to a file."""
    handler_yaml = '\n'.join(
        GenerateYamlHandlersList(app_engine_web_xml, web_xml, static_files))
    return handler_yaml + '\n'
  
  
  class HandlerGenerator(object):
    """Ancestor class for StaticHandlerGenerator and DynamicHandlerGenerator.
  
    Contains shared functionality. Both static and dynamic handler generators
    work in a similar way. Both obtain a list of patterns, static includes and
    web.xml servlet url patterns, respectively, add security constraint info to
    those lists, sort them, and then generate Yaml statements for each entry.
    """
  
    def __init__(self, app_engine_web_xml, web_xml):
      self.app_engine_web_xml = app_engine_web_xml
      self.web_xml = web_xml
  
    def GetHandlerYaml(self):
      handler_yaml = []
      for h in self.GenerateOrderedHandlerList():
        handler_yaml += self.TranslateHandler(h)
      return handler_yaml
  
    def GenerateSecurityConstraintHandlers(self):
      """Creates Handlers for security constraint information."""
      handler_list = []
      for constraint in self.web_xml.security_constraints:
        props = {'transport_guarantee': constraint.transport_guarantee,
                 'required_role': constraint.required_role}
        for pattern in constraint.patterns:
          security_handler = handler.SimpleHandler(pattern, props)
          handler_list.append(security_handler)
          handler_list += self.CreateHandlerWithoutTrailingStar(security_handler)
      return handler_list
  
    def GenerateWelcomeFileHandlers(self):
      """Creates handlers for welcome files."""
      if not self.welcome_properties:
        return []
  
  
  
      return [handler.SimpleHandler('/', self.welcome_properties),
              handler.SimpleHandler('/*/', self.welcome_properties)]
  
    def TranslateAdditionalOptions(self, h):
      """Generates Yaml statements from security constraint information."""
      additional_statements = []
  
      required_role = h.GetProperty('required_role', default='none')
      required_role_translation = {'none': 'optional',
                                   '*': 'required',
                                   'admin': 'admin'}
      additional_statements.append(
          '  login: %s' % required_role_translation[required_role])
  
      transport_guarantee = h.GetProperty('transport_guarantee', default='none')
  
      if transport_guarantee == 'none':
        if self.app_engine_web_xml.ssl_enabled:
          additional_statements.append('  secure: optional')
        else:
          additional_statements.append('  secure: never')
      else:
        if self.app_engine_web_xml.ssl_enabled:
          additional_statements.append('  secure: always')
        else:
          raise AppEngineConfigException(
              'SSL must be enabled in appengine-web.xml to use '
              'transport-guarantee')
  
      handler_id = self.web_xml.pattern_to_id.get(h.pattern)
      if handler_id and handler_id in self.app_engine_web_xml.api_endpoints:
        additional_statements.append('  api_endpoint: True')
      return additional_statements
  
    def CreateHandlerWithoutTrailingStar(self, h):
      """Creates identical handler without trailing star in pattern.
  
      According to servlet spec, baz/* should match baz.
  
      Args:
        h: a Handler.
      Returns:
        If h.pattern is of form "baz/*", returns a singleton list with a
        SimpleHandler with pattern "baz" and the properties of h. Otherwise,
        returns an empty list.
      """
      if len(h.pattern) <= 2 or h.pattern[-2:] != '/*':
        return []
      return [handler.SimpleHandler(h.pattern[:-2], h.properties)]
  
  
  class DynamicHandlerGenerator(HandlerGenerator):
    """Generates dynamic handler yaml entries for app.yaml."""
  
    def __init__(self, app_engine_web_xml, web_xml):
      super(DynamicHandlerGenerator, self).__init__(app_engine_web_xml, web_xml)
      if any([self.web_xml.fall_through_to_runtime,
              '/' in self.web_xml.patterns,
              '/*' in self.web_xml.patterns]):
        self.fall_through = True
        self.welcome_properties = {}
      else:
        self.fall_through = False
        self.welcome_properties = {'type': 'dynamic'}
  
      self.has_api_endpoint = API_ENDPOINT_REGEX in self.web_xml.patterns
      self.patterns = [pattern for pattern in self.web_xml.patterns if
                       pattern not in ('/', '/*', API_ENDPOINT_REGEX)]
  
    def MakeServletPatternsIntoHandlers(self):
      """Creates SimpleHandlers from servlet and filter mappings in web.xml."""
      handler_patterns = []
      has_jsps = False
      if self.fall_through:
        return [handler.SimpleHandler('/*', {'type': 'dynamic'})]
  
      for pattern in self.patterns:
        if pattern.endswith('.jsp'):
          has_jsps = True
        else:
          new_handler = handler.SimpleHandler(pattern, {'type': 'dynamic'})
          handler_patterns.append(new_handler)
          handler_patterns += self.CreateHandlerWithoutTrailingStar(new_handler)
  
      if has_jsps or self.app_engine_web_xml.vm:
        handler_patterns.append(
            handler.SimpleHandler('*.jsp', {'type': 'dynamic'}))
  
      handler_patterns.append(
          handler.SimpleHandler('/_ah/*', {'type': 'dynamic'}))
      return handler_patterns
  
    def GenerateOrderedHandlerList(self):
      handler_patterns = (self.MakeServletPatternsIntoHandlers()
                          + self.GenerateSecurityConstraintHandlers()
                          + self.GenerateWelcomeFileHandlers())
      ordered_handlers = handler.GetOrderedIntersection(handler_patterns)
      if self.has_api_endpoint:
        ordered_handlers.append(
            handler.SimpleHandler(API_ENDPOINT_REGEX, {'type': 'dynamic'}))
      return ordered_handlers
  
    def TranslateHandler(self, the_handler):
      """Converts a dynamic handler object to Yaml."""
      if the_handler.GetProperty('type') != 'dynamic':
        return []
      statements = ['- url: %s' % the_handler.Regexify(),
                    '  script: unused']
      return statements + self.TranslateAdditionalOptions(the_handler)
  
  
  class StaticHandlerGenerator(HandlerGenerator):
    """Generates static handler yaml entries for app.yaml."""
  
    def __init__(self, app_engine_web_xml, web_xml, static_files):
      super(StaticHandlerGenerator, self).__init__(app_engine_web_xml, web_xml)
      self.static_files = static_files
      static_welcome_files = []
      for welcome_file in self.web_xml.welcome_files:
        for static_file in static_files:
          if static_file.endswith('/' + welcome_file):
            static_welcome_files.append(welcome_file)
            break
  
      welcome_value = tuple(static_welcome_files) or None
      self.welcome_properties = {'welcome': welcome_value}
  
    def MakeStaticFilePatternsIntoHandlers(self):
      """Creates SimpleHandlers out of XML-specified static file includes."""
      includes = self.app_engine_web_xml.static_file_includes
      if not includes:
        return [handler.SimpleHandler('/*', {'type': 'static'})]
  
      handler_patterns = []
  
      for include in includes:
        pattern = include.pattern.replace('**', '*')
        if pattern[0] != '/':
          pattern = '/' + pattern
        properties = {'type': 'static'}
        if include.expiration:
          properties['expiration'] = include.expiration
        if include.http_headers:
  
          properties['http_headers'] = tuple(sorted(include.http_headers.items()))
        handler_patterns.append(handler.SimpleHandler(pattern, properties))
      return handler_patterns
  
    def GenerateOrderedHandlerList(self):
      handler_patterns = (self.MakeStaticFilePatternsIntoHandlers()
                          + self.GenerateSecurityConstraintHandlers()
                          + self.GenerateWelcomeFileHandlers())
      return handler.GetOrderedIntersection(handler_patterns)
  
    def TranslateHandler(self, h):
      """Translates SimpleHandler to static handler yaml statements."""
      root = self.app_engine_web_xml.public_root
      regex_string = h.Regexify()
      if regex_string.startswith(root):
        regex_string = regex_string[len(root):]
  
      welcome_files = h.GetProperty('welcome')
  
      if welcome_files:
        statements = []
        for welcome_file in welcome_files:
          statements += ['- url: (%s)' % regex_string,
                         '  static_files: __static__%s\\1%s' %
                         (root, welcome_file),
                         '  upload: __NOT_USED__',
                         '  require_matching_file: True']
          statements += self.TranslateAdditionalOptions(h)
          statements += self.TranslateAdditionalStaticOptions(h)
        return statements
  
      if h.GetProperty('type') != 'static':
        return []
  
      statements = ['- url: (%s)' % regex_string,
                    '  static_files: __static__%s\\1' % root,
                    '  upload: __NOT_USED__',
                    '  require_matching_file: True']
  
      return (statements +
              self.TranslateAdditionalOptions(h) +
              self.TranslateAdditionalStaticOptions(h))
  
    def TranslateAdditionalStaticOptions(self, h):
      statements = []
      expiration = h.GetProperty('expiration')
      if expiration:
        statements.append('  expiration: %s' % expiration)
      http_headers = h.GetProperty('http_headers')
      if http_headers:
        statements.append('  http_headers:')
        statements += ['    %s: %s' % pair for pair in http_headers]
      return statements
  