def _get_document(self, trans, uri, check_etag): username, domain = uri.user.username, uri.user.domain self._normalize_document_path(uri) doc_type = self.app_mapping[uri.application_id] query = """SELECT doc, etag FROM %(table)s WHERE username = %%(username)s AND domain = %%(domain)s AND doc_type= %%(doc_type)s AND doc_uri=%%(document_path)s""" % { "table": Config.xcap_table } params = { "username": username, "domain": domain, "doc_type": doc_type, "document_path": uri.doc_selector.document_path } trans.execute(query, params) result = trans.fetchall() if len(result) > 1: raise MultipleResultsError(params) elif result: doc, etag = result[0] if isinstance(doc, unicode): doc = doc.encode('utf-8') check_etag(etag) response = StatusResponse(200, etag, doc) else: response = StatusResponse(404) return response
class Storage(object): __metaclass__ = Singleton def __init__(self): self._database = DatabaseConnection() self._provisioning = XCAPProvisioning() self._sip_notifier = SIPNotifier() self._notifier = Notifier(ServerConfig.root, self._sip_notifier.send_publish) def _normalize_document_path(self, uri): if uri.application_id in ("pres-rules", "org.openmobilealliance.pres-rules"): # some clients e.g. counterpath's eyebeam save presence rules under # different filenames between versions and they expect to find the same # information, thus we are forcing all presence rules documents to be # saved under "index.xml" default filename uri.doc_selector.document_path = "index.xml" def get_document(self, uri, check_etag): self._normalize_document_path(uri) result = self._database.get(uri) result.addCallback(self._got_document, check_etag) result.addErrback(self._eb_not_found) return result def _eb_not_found(self, failure): failure.trap(NotFound) return StatusResponse(404) def _got_document(self, (doc, etag), check_etag): check_etag(etag) return StatusResponse(200, etag, doc.encode('utf-8'))
def _cb_put(self, result, uri, thor_key): if result[0]: code = 200 else: code = 201 self._provisioning.notify("update", "sip_account", thor_key) self._notifier.on_change(uri, result[1], result[2]) return StatusResponse(code, result[2])
def _delete_document(self, trans, uri, check_etag): username, domain = uri.user.username, uri.user.domain self._normalize_document_path(uri) doc_type = self.app_mapping[uri.application_id] document_path = uri.doc_selector.document_path query = """SELECT etag FROM %(table)s WHERE username = %%(username)s AND domain = %%(domain)s AND doc_type= %%(doc_type)s AND doc_uri = %%(document_path)s""" % { "table": Config.xcap_table } params = { "username": username, "domain": domain, "doc_type": doc_type, "document_path": document_path } trans.execute(query, params) result = trans.fetchall() if len(result) > 1: raise MultipleResultsError(params) elif result: etag = result[0][0] check_etag(etag) query = """DELETE FROM %(table)s WHERE username = %%(username)s AND domain = %%(domain)s AND doc_type= %%(doc_type)s AND doc_uri = %%(document_path)s AND etag = %%(etag)s""" % { "table": Config.xcap_table } params = { "username": username, "domain": domain, "doc_type": doc_type, "document_path": document_path, "etag": etag } trans.execute(query, params) deleted = getattr(trans._connection, 'affected_rows', lambda: 1)() if not deleted: # the document was replaced/removed after the SELECT but before the DELETE raise DeleteFailed assert deleted == 1, deleted return StatusResponse(200, old_etag=etag) else: return StatusResponse(404)
def _cb_get_element(self, response, uri): """This is called when the document related to the element is retrieved.""" if response.code == 404: ## XXX why not let the storage raise? raise errors.ResourceNotFound("The requested document %s was not found on this server" % uri.doc_selector) result = element.get(response.data, uri.node_selector.element_selector) if not result: msg = "The requested element %s was not found in the document %s" % (uri.node_selector, uri.doc_selector) raise errors.ResourceNotFound(msg) return StatusResponse(200, response.etag, result)
def _delete_all_documents(self, trans, uri): username, domain = uri.user.username, uri.user.domain query = """DELETE FROM %(table)s WHERE username = %%(username)s AND domain = %%(domain)s """ % { "table": Config.xcap_table } params = {"username": username, "domain": domain} trans.execute(query, params) return StatusResponse(200)
def _watchers_to_xml(self, watchers, uri, check_etag): root = etree.Element("watchers", nsmap={None: self.default_ns}) for watcher in watchers: watcher_elem = etree.SubElement(root, "watcher") for name, value in watcher.iteritems(): etree.SubElement(watcher_elem, name).text = value doc = etree.tostring(root, encoding="utf-8", pretty_print=True, xml_declaration=True) #self.validate_document(doc) etag = make_etag(uri, doc) check_etag(etag) return StatusResponse(200, data=doc, etag=etag)
def _docs_to_xml(self, docs, uri): sip_uri = "sip:%s@%s" % (uri.user.username, uri.user.domain) root = etree.Element("xcap-directory", nsmap={None: self.default_ns}) if docs: for k, v in docs.iteritems(): folder = etree.SubElement(root, "folder", attrib={'auid': k}) for item in v: # We may have more than one document for the same application entry_uri = "%s/%s/users/%s/%s" % (uri.xcap_root, k, sip_uri, item[0]) entry = etree.SubElement(folder, "entry") entry.set("uri", entry_uri) entry.set("etag", item[1]) doc = etree.tostring(root, encoding="UTF-8", pretty_print=True, xml_declaration=True) #self.validate_document(doc) return defer.succeed(StatusResponse(200, etag=None, data=doc))
application = getApplicationForURI(uri) ns_dict = uri.node_selector.get_ns_bindings(application.default_ns) try: xpath = uri.node_selector.replace_default_prefix() attribute = xml_doc.xpath(xpath, namespaces = ns_dict) except Exception, ex: ex.http_error = errors.ResourceNotFound() raise if not attribute: raise errors.ResourceNotFound elif len(attribute) != 1: raise errors.ResourceNotFound('XPATH expression is ambiguous') # TODO # The server MUST NOT add namespace bindings representing namespaces # used by the element or its children, but declared in ancestor elements return StatusResponse(200, response.etag, attribute[0]) def get_attribute(self, uri, check_etag): d = self.get_document(uri, check_etag) return d.addCallbacks(self._cb_get_attribute, callbackArgs=(uri, )) def _cb_delete_attribute(self, response, uri, check_etag): if response.code == 404: raise errors.ResourceNotFound document = response.data xml_doc = etree.parse(StringIO(document)) application = getApplicationForURI(uri) ns_dict = uri.node_selector.get_ns_bindings(application.default_ns) try: elem = xml_doc.xpath(uri.node_selector.replace_default_prefix(append_terminal=False),namespaces=ns_dict) except Exception, ex:
def _cb_delete(self, result, uri, thor_key): self._provisioning.notify("update", "sip_account", thor_key) self._notifier.on_change(uri, result[1], None) return StatusResponse(200)
def _cb_delete_all(self, result, uri, thor_key): self._provisioning.notify("update", "sip_account", thor_key) return StatusResponse(200)
def _eb_not_found(self, failure): failure.trap(NotFound) return StatusResponse(404)
def _put_document(self, trans, uri, document, check_etag): username, domain = uri.user.username, uri.user.domain self._normalize_document_path(uri) doc_type = self.app_mapping[uri.application_id] document_path = uri.doc_selector.document_path query = """SELECT etag FROM %(table)s WHERE username = %%(username)s AND domain = %%(domain)s AND doc_type= %%(doc_type)s AND doc_uri=%%(document_path)s""" % { "table": Config.xcap_table } params = { "username": username, "domain": domain, "doc_type": doc_type, "document_path": document_path } trans.execute(query, params) result = trans.fetchall() if len(result) > 1: raise MultipleResultsError(params) elif not result: check_etag(None, False) ## the document doesn't exist, create it etag = make_random_etag(uri) query = """INSERT INTO %(table)s (username, domain, doc_type, etag, doc, doc_uri) VALUES (%%(username)s, %%(domain)s, %%(doc_type)s, %%(etag)s, %%(document)s, %%(document_path)s)""" % { "table": Config.xcap_table } params = { "username": username, "domain": domain, "doc_type": doc_type, "etag": etag, "document": document, "document_path": document_path } # may raise IntegrityError here, if the document was created in another connection # will be catched by repeat_on_error trans.execute(query, params) return StatusResponse(201, etag) else: old_etag = result[0][0] ## first check the etag of the existing resource check_etag(old_etag) ## the document exists, replace it etag = make_random_etag(uri) query = """UPDATE %(table)s SET doc = %%(document)s, etag = %%(etag)s WHERE username = %%(username)s AND domain = %%(domain)s AND doc_type = %%(doc_type)s AND etag = %%(old_etag)s AND doc_uri = %%(document_path)s""" % { "table": Config.xcap_table } params = { "document": document, "etag": etag, "username": username, "domain": domain, "doc_type": doc_type, "old_etag": old_etag, "document_path": document_path } trans.execute(query, params) # the request may not update anything (e.g. if etag was changed by another connection # after we did SELECT); if so, we should retry updated = getattr(trans._connection, 'affected_rows', lambda: 1)() if not updated: raise UpdateFailed assert updated == 1, updated return StatusResponse(200, etag, old_etag=old_etag)
def _purge_cb(self, result, uri): return StatusResponse(200)
def get_document_global(self, uri, check_etag): doc, etag = self._get_document() return defer.succeed(StatusResponse(200, etag=etag, data=doc))