ldp_rs.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. from copy import deepcopy
  2. from rdflib import Graph
  3. from rdflib.namespace import RDF, XSD
  4. from rdflib.plugins.sparql.parser import parseUpdate
  5. from rdflib.term import URIRef, Literal, Variable
  6. from lakesuperior.dictionaries.namespaces import ns_collection as nsc
  7. from lakesuperior.dictionaries.srv_mgd_terms import srv_mgd_subjects, \
  8. srv_mgd_predicates, srv_mgd_types
  9. from lakesuperior.model.ldpr import Ldpr, transactional, must_exist
  10. from lakesuperior.exceptions import ResourceNotExistsError, \
  11. ServerManagedTermError, SingleSubjectError
  12. from lakesuperior.util.translator import Translator
  13. class LdpRs(Ldpr):
  14. '''LDP-RS (LDP RDF source).
  15. Definition: https://www.w3.org/TR/ldp/#ldprs
  16. '''
  17. RETURN_CHILD_RES_URI = nsc['fcrepo'].EmbedResources
  18. RETURN_INBOUND_REF_URI = nsc['fcrepo'].InboundReferences
  19. RETURN_SRV_MGD_RES_URI = nsc['fcrepo'].ServerManaged
  20. base_types = {
  21. nsc['ldp'].RDFSource
  22. }
  23. std_headers = {
  24. 'Accept-Post' : {
  25. 'text/turtle',
  26. 'text/rdf+n3',
  27. 'text/n3',
  28. 'application/rdf+xml',
  29. 'application/n-triples',
  30. 'application/ld+json',
  31. 'multipart/form-data',
  32. 'application/sparql-update',
  33. },
  34. 'Accept-Patch' : {
  35. 'application/sparql-update',
  36. },
  37. }
  38. def head(self):
  39. '''
  40. Return values for the headers.
  41. '''
  42. headers = self.rdfly.headers
  43. for t in self.ldp_types:
  44. headers['Link'].append('{};rel="type"'.format(t.identifier.n3()))
  45. return headers
  46. def get(self, pref_return):
  47. '''
  48. https://www.w3.org/TR/ldp/#ldpr-HTTP_GET
  49. '''
  50. kwargs = {}
  51. minimal = embed_children = incl_inbound = False
  52. kwargs['incl_srv_mgd'] = True
  53. if 'value' in pref_return and pref_return['value'] == 'minimal':
  54. kwargs['minimal'] = True
  55. else:
  56. include = pref_return['parameters']['include'].split(' ') \
  57. if 'include' in pref_return['parameters'] else []
  58. omit = pref_return['parameters']['omit'].split(' ') \
  59. if 'omit' in pref_return['parameters'] else []
  60. self._logger.debug('Include: {}'.format(include))
  61. self._logger.debug('Omit: {}'.format(omit))
  62. if str(self.RETURN_INBOUND_REF_URI) in include:
  63. kwargs['incl_inbound'] = True
  64. if str(self.RETURN_CHILD_RES_URI) in omit:
  65. kwargs['embed_chldren'] = False
  66. if str(self.RETURN_SRV_MGD_RES_URI) in omit:
  67. kwargs['incl_srv_mgd'] = False
  68. im_rsrc = self.rdfly.out_rsrc(**kwargs)
  69. if not len(im_rsrc.graph):
  70. raise ResourceNotExistsError(im_rsrc.uuid)
  71. return Translator.globalize_rsrc(im_rsrc)
  72. @transactional
  73. def post(self, data, format='text/turtle', handling=None):
  74. '''
  75. https://www.w3.org/TR/ldp/#ldpr-HTTP_POST
  76. Perform a POST action after a valid resource URI has been found.
  77. '''
  78. g = Graph().parse(data=data, format=format, publicID=self.urn)
  79. g = self._check_mgd_terms(g, handling)
  80. self._ensure_single_subject_rdf(g)
  81. for t in self.base_types:
  82. g.add((self.urn, RDF.type, t))
  83. self.rdfly.create_rsrc(g)
  84. self._set_containment_rel()
  85. @transactional
  86. def put(self, data, format='text/turtle', handling=None):
  87. '''
  88. https://www.w3.org/TR/ldp/#ldpr-HTTP_PUT
  89. '''
  90. g = Graph().parse(data=data, format=format, publicID=self.urn)
  91. g = self._check_mgd_terms(g, handling)
  92. self._ensure_single_subject_rdf(g)
  93. for t in self.base_types:
  94. g.add((self.urn, RDF.type, t))
  95. res = self.rdfly.create_or_replace_rsrc(g)
  96. self._set_containment_rel()
  97. return res
  98. @transactional
  99. @must_exist
  100. def patch(self, data):
  101. '''
  102. https://www.w3.org/TR/ldp/#ldpr-HTTP_PATCH
  103. '''
  104. remove, add = self._sparql_delta(data)
  105. self.rdfly.modify_rsrc(remove, add)
  106. ## PROTECTED METHODS ##
  107. def _check_mgd_terms(self, g, handling='strict'):
  108. '''
  109. Check whether server-managed terms are in a RDF payload.
  110. '''
  111. offending_subjects = set(g.subjects()) & srv_mgd_subjects
  112. if offending_subjects:
  113. if handling=='strict':
  114. raise ServerManagedTermError(offending_subjects, 's')
  115. else:
  116. for s in offending_subjects:
  117. g.remove((s, Variable('p'), Variable('o')))
  118. offending_predicates = set(g.predicates()) & srv_mgd_predicates
  119. if offending_predicates:
  120. if handling=='strict':
  121. raise ServerManagedTermError(offending_predicates, 'p')
  122. else:
  123. for p in offending_predicates:
  124. g.remove((Variable('s'), p, Variable('o')))
  125. offending_types = set(g.objects(predicate=RDF.type)) & srv_mgd_types
  126. if offending_types:
  127. if handling=='strict':
  128. raise ServerManagedTermError(offending_types, 't')
  129. else:
  130. for t in offending_types:
  131. g.remove((Variable('s'), RDF.type, t))
  132. return g
  133. def _sparql_delta(self, q, handling=None):
  134. '''
  135. Calculate the delta obtained by a SPARQL Update operation.
  136. This does a couple of extra things:
  137. 1. It ensures that no resources outside of the subject of the request
  138. are modified (e.g. by variable subjects)
  139. 2. It verifies that none of the terms being modified is server-managed.
  140. This method extracts an in-memory copy of the resource and performs the
  141. query on that once it has checked if any of the server managed terms is
  142. in the delta. If it is, it raises an exception.
  143. NOTE: This only checks if a server-managed term is effectively being
  144. modified. If a server-managed term is present in the query but does not
  145. cause any change in the updated resource, no error is raised.
  146. @return tuple Remove and add triples. These can be used with
  147. `BaseStoreLayout.update_resource`.
  148. '''
  149. pre_g = self.rdfly.extract_imr().graph
  150. post_g = deepcopy(pre_g)
  151. post_g.update(q)
  152. remove = pre_g - post_g
  153. add = post_g - pre_g
  154. self._logger.info('Removing: {}'.format(
  155. remove.serialize(format='turtle').decode('utf8')))
  156. self._logger.info('Adding: {}'.format(
  157. add.serialize(format='turtle').decode('utf8')))
  158. remove = self._check_mgd_terms(remove, handling)
  159. add = self._check_mgd_terms(add, handling)
  160. return remove, add
  161. def _ensure_single_subject_rdf(self, g):
  162. '''
  163. Ensure that a RDF payload for a POST or PUT has a single resource.
  164. '''
  165. for s in set(g.subjects()):
  166. if not s == self.uri:
  167. return SingleSubjectError(s, self.uri)
  168. class Ldpc(LdpRs):
  169. '''LDPC (LDP Container).'''
  170. def __init__(self, uuid):
  171. super().__init__(uuid)
  172. self.base_types.update({
  173. nsc['ldp'].Container,
  174. })
  175. class LdpBc(Ldpc):
  176. '''LDP-BC (LDP Basic Container).'''
  177. def __init__(self, uuid):
  178. super().__init__(uuid)
  179. self.base_types.update({
  180. nsc['ldp'].BasicContainer,
  181. })
  182. class LdpDc(Ldpc):
  183. '''LDP-DC (LDP Direct Container).'''
  184. def __init__(self, uuid):
  185. super().__init__(uuid)
  186. self.base_types.update({
  187. nsc['ldp'].DirectContainer,
  188. })
  189. class LdpIc(Ldpc):
  190. '''LDP-IC (LDP Indirect Container).'''
  191. def __init__(self, uuid):
  192. super().__init__(uuid)
  193. self.base_types.update({
  194. nsc['ldp'].IndirectContainer,
  195. })