def get(self, user_id=None):
    """Args:
      contacts: list of PoCo contact dicts
    """
    paging_params = self.get_paging_params()
    total_results, contacts = self.source.get_contacts(user_id, **paging_params)

    response = {'startIndex': paging_params['start_index'],
                'itemsPerPage': len(contacts),
                'totalResults': total_results,
                'entry': contacts,
                'filtered': False,
                'sorted': False,
                'updatedSince': False,
                }

    self.response.headers['Access-Control-Allow-Origin'] = '*'

    format = self.request.get('format', 'json')
    if format == 'json':
      self.response.headers['Content-Type'] = 'application/json'
      self.response.out.write(json.dumps(response, indent=2))
    elif format == 'xml':
      self.response.headers['Content-Type'] = 'text/xml'
      self.response.out.write(XML_TEMPLATE % util.to_xml(response))
    else:
      raise exc.HTTPBadRequest('Invalid format: %s (should be json or xml)' %
                               format)
  def get(self):
    """Handles an API GET.

    Request path is of the form /user_id/group_id/app_id/activity_id , where
    each element is an optional string object id.
    """
    args = {key: self.request.params[key]
            for key in ('access_token', 'access_token_key', 'access_token_secret')
            if key in self.request.params}
    source = self.source_class()(**args)

    # parse path
    args = urllib.unquote(self.request.path).strip('/').split('/')
    if len(args) > MAX_PATH_LEN:
      raise exc.HTTPNotFound()
    elif args == ['']:
      args = []

    # handle default path elements
    args = [None if a in defaults else a
            for a, defaults in zip(args, PATH_DEFAULTS)]
    user_id = args[0] if args else None
    paging_params = self.get_paging_params()

    # extract format
    format = self.request.get('format', 'json')
    if format not in ('json', 'atom', 'xml'):
      raise exc.HTTPBadRequest('Invalid format: %s, expected json, atom, xml' %
                               format)

    # get activities and build response
    total_results, activities = source.get_activities(*args, **paging_params)

    response = {'startIndex': paging_params['start_index'],
                'itemsPerPage': len(activities),
                'totalResults': total_results,
                # TODO: this is just for compatibility with
                # http://activitystreamstester.appspot.com/
                # the OpenSocial spec says to use entry instead, so switch back
                # to that eventually
                'items': activities,
                'filtered': False,
                'sorted': False,
                'updatedSince': False,
                }

    if format == 'atom':
      # strip the access token from the request URL before returning
      params = dict(self.request.GET.items())
      if 'access_token' in params:
        del params['access_token']
      request_url = '%s?%s' % (self.request.path_url, urllib.urlencode(params))
      actor = source.get_actor(user_id)
      response.update({
          'host_url': self.request.host_url + "/",
          'request_url': request_url,
          'title': 'User feed for ' + actor.get('displayName', 'unknown'),
          'updated': activities[0]['object'].get('published') if activities else '',
          'actor': actor,
          })

    # encode and write response
    self.response.headers['Access-Control-Allow-Origin'] = '*'
    if format == 'json':
      self.response.headers['Content-Type'] = 'application/json'
      self.response.out.write(json.dumps(response, indent=2))
    else:
      self.response.headers['Content-Type'] = 'text/xml'
      if format == 'xml':
        self.response.out.write(XML_TEMPLATE % util.to_xml(response))
      else:
        assert format == 'atom'
        self.response.out.write(template.render(ATOM_TEMPLATE_FILE, response))

    if 'plaintext' in self.request.params:
      # override response content type
      self.response.headers['Content-Type'] = 'text/plain'