ldp_rs.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. #from copy import deepcopy
  2. from flask import current_app, g
  3. from rdflib import Graph
  4. from rdflib.plugins.sparql.algebra import translateUpdate
  5. from rdflib.plugins.sparql.parser import parseUpdate
  6. from lakesuperior.dictionaries.namespaces import ns_collection as nsc
  7. from lakesuperior.model.ldpr import Ldpr, atomic
  8. class LdpRs(Ldpr):
  9. '''LDP-RS (LDP RDF source).
  10. Definition: https://www.w3.org/TR/ldp/#ldprs
  11. '''
  12. def __init__(self, uuid, repr_opts={}, handling='lenient', **kwargs):
  13. '''
  14. Extends Ldpr.__init__ by adding LDP-RS specific parameters.
  15. @param handling (string) One of `strict`, `lenient` (the default) or
  16. `none`. `strict` raises an error if a server-managed term is in the
  17. graph. `lenient` removes all sever-managed triples encountered. `none`
  18. skips all server-managed checks. It is used for internal modifications.
  19. '''
  20. super().__init__(uuid, **kwargs)
  21. self.base_types = super().base_types | {
  22. nsc['fcrepo'].Container,
  23. nsc['ldp'].Container,
  24. }
  25. # provided_imr can be empty. If None, it is an outbound resource.
  26. if self.provided_imr is not None:
  27. self.workflow = self.WRKF_INBOUND
  28. else:
  29. self.workflow = self.WRKF_OUTBOUND
  30. self._imr_options = repr_opts
  31. self.handling = handling
  32. ## LDP METHODS ##
  33. @atomic
  34. def patch(self, update_str):
  35. '''
  36. https://www.w3.org/TR/ldp/#ldpr-HTTP_PATCH
  37. Update an existing resource by applying a SPARQL-UPDATE query.
  38. @param update_str (string) SPARQL-Update staements.
  39. '''
  40. self.handling = 'strict'
  41. local_update_str = g.tbox.localize_ext_str(update_str, self.urn)
  42. return self._sparql_update(local_update_str)
  43. def _sparql_update(self, update_str, notify=True):
  44. '''
  45. Apply a SPARQL update to a resource.
  46. The SPARQL string is validated beforehand to make sure that it does
  47. not contain server-managed terms.
  48. In theory, server-managed terms in DELETE statements are harmless
  49. because the patch is only applied over the user-provided triples, but
  50. at the moment those are also checked.
  51. '''
  52. # Parse the SPARQL update string and validate contents.
  53. qry_struct = translateUpdate(parseUpdate(update_str))
  54. check_ins_gr = Graph()
  55. check_del_gr = Graph()
  56. for stmt in qry_struct:
  57. try:
  58. check_ins_gr += set(stmt.insert.triples)
  59. except AttributeError:
  60. pass
  61. try:
  62. check_del_gr += set(stmt.delete.triples)
  63. except AttributeError:
  64. pass
  65. self._check_mgd_terms(check_ins_gr)
  66. self._check_mgd_terms(check_del_gr)
  67. self.rdfly.patch_rsrc(self.uid, update_str)
  68. if notify and current_app.config.get('messaging'):
  69. self._send_msg(self.RES_UPDATED, check_del_gr, check_ins_gr)
  70. return self.RES_UPDATED
  71. #def _sparql_delta(self, q):
  72. # '''
  73. # Calculate the delta obtained by a SPARQL Update operation.
  74. # This is a critical component of the SPARQL update prcess and does a
  75. # couple of things:
  76. # 1. It ensures that no resources outside of the subject of the request
  77. # are modified (e.g. by variable subjects)
  78. # 2. It verifies that none of the terms being modified is server managed.
  79. # This method extracts an in-memory copy of the resource and performs the
  80. # query on that once it has checked if any of the server managed terms is
  81. # in the delta. If it is, it raises an exception.
  82. # NOTE: This only checks if a server-managed term is effectively being
  83. # modified. If a server-managed term is present in the query but does not
  84. # cause any change in the updated resource, no error is raised.
  85. # @return tuple(rdflib.Graph) Remove and add graphs. These can be used
  86. # with `BaseStoreLayout.update_resource` and/or recorded as separate
  87. # events in a provenance tracking system.
  88. # '''
  89. # self._logger.debug('Provided SPARQL query: {}'.format(q))
  90. # pre_gr = self.imr.graph
  91. # post_gr = pre_gr | Graph()
  92. # post_gr.update(q)
  93. # remove_gr, add_gr = self._dedup_deltas(pre_gr, post_gr)
  94. # #self._logger.debug('Removing: {}'.format(
  95. # # remove_gr.serialize(format='turtle').decode('utf8')))
  96. # #self._logger.debug('Adding: {}'.format(
  97. # # add_gr.serialize(format='turtle').decode('utf8')))
  98. # remove_gr = self._check_mgd_terms(remove_gr)
  99. # add_gr = self._check_mgd_terms(add_gr)
  100. # return set(remove_gr), set(add_gr)
  101. class Ldpc(LdpRs):
  102. '''LDPC (LDP Container).'''
  103. def __init__(self, uuid, *args, **kwargs):
  104. super().__init__(uuid, *args, **kwargs)
  105. self.base_types |= {
  106. nsc['fcrepo'].Container,
  107. nsc['ldp'].Container,
  108. }
  109. class LdpBc(Ldpc):
  110. '''LDP-BC (LDP Basic Container).'''
  111. def __init__(self, uuid, *args, **kwargs):
  112. super().__init__(uuid, *args, **kwargs)
  113. self.base_types |= {
  114. nsc['ldp'].BasicContainer,
  115. }
  116. class LdpDc(Ldpc):
  117. '''LDP-DC (LDP Direct Container).'''
  118. def __init__(self, uuid, *args, **kwargs):
  119. super().__init__(uuid, *args, **kwargs)
  120. self.base_types |= {
  121. nsc['ldp'].DirectContainer,
  122. }
  123. class LdpIc(Ldpc):
  124. '''LDP-IC (LDP Indirect Container).'''
  125. def __init__(self, uuid, *args, **kwargs):
  126. super().__init__(uuid, *args, **kwargs)
  127. self.base_types |= {
  128. nsc['ldp'].IndirectContainer,
  129. }