simple_strategy.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. from copy import deepcopy
  2. import arrow
  3. from rdflib import Graph
  4. from rdflib.namespace import XSD
  5. from rdflib.resource import Resource
  6. from rdflib.term import Literal, URIRef, Variable
  7. from lakesuperior.core.namespaces import ns_collection as nsc
  8. from lakesuperior.core.namespaces import ns_mgr as nsm
  9. from lakesuperior.store_strategies.rdf.base_rdf_strategy import BaseRdfStrategy
  10. from lakesuperior.util.digest import Digest
  11. class SimpleStrategy(BaseRdfStrategy):
  12. '''
  13. This is the simplest strategy.
  14. It uses a flat triple structure without named graphs aimed at performance.
  15. Changes are destructive.
  16. In theory it could be used on top of a triplestore instead of a quad-store
  17. for (possible) improved speed and reduced storage.
  18. '''
  19. @property
  20. def headers(self):
  21. '''
  22. See base_rdf_strategy.headers.
  23. '''
  24. headers = {
  25. 'Link' : [],
  26. }
  27. digest = self.rsrc.value(nsc['premis'].hasMessageDigest)
  28. if digest:
  29. etag = digest.identifier.split(':')[-1]
  30. headers['ETag'] = 'W/"{}"'.format(etag),
  31. last_updated_term = self.rsrc.value(nsc['fedora'].lastUpdated)
  32. if last_updated_term:
  33. headers['Last-Modified'] = arrow.get(last_updated_term)\
  34. .format('ddd, D MMM YYYY HH:mm:ss Z')
  35. return headers
  36. def out_graph(self, inbound=False):
  37. '''
  38. See base_rdf_strategy.out_graph.
  39. '''
  40. inbound_qry = '\n?s1 ?p1 {}'.format(self.base_urn.n3()) \
  41. if inbound else ''
  42. q = '''
  43. CONSTRUCT {{
  44. {0} ?p ?o .{1}
  45. }} WHERE {{
  46. {0} ?p ?o .{1}
  47. }}
  48. '''.format(self.base_urn.n3(), inbound_qry)
  49. qres = self.rsrc.graph.query(q)
  50. return Resource(qres.graph, self.base_urn)
  51. def ask_rsrc_exists(self, rsrc=None):
  52. '''
  53. See base_rdf_strategy.ask_rsrc_exists.
  54. '''
  55. if not rsrc:
  56. if self.rsrc is not None:
  57. rsrc = self.rsrc
  58. else:
  59. return False
  60. self._logger.info('Searching for resource: {}'
  61. .format(rsrc.identifier))
  62. return (rsrc.identifier, Variable('p'), Variable('o')) in self.ds
  63. def create_or_replace_rsrc(self, g):
  64. '''
  65. See base_rdf_strategy.create_or_replace_rsrc.
  66. '''
  67. # @TODO Use gunicorn to get request timestamp.
  68. ts = Literal(arrow.utcnow(), datatype=XSD.dateTime)
  69. if self.ask_rsrc_exists():
  70. self._logger.info(
  71. 'Resource {} exists. Removing all outbound triples.'
  72. .format(self.rsrc.identifier))
  73. # Delete all triples but keep creation date and creator.
  74. created = self.rsrc.value(nsc['fedora'].created)
  75. created_by = self.rsrc.value(nsc['fedora'].createdBy)
  76. self.delete_rsrc()
  77. else:
  78. created = ts
  79. created_by = Literal('BypassAdmin')
  80. self.rsrc.set(nsc['fedora'].created, created)
  81. self.rsrc.set(nsc['fedora'].createdBy, created_by)
  82. self.rsrc.set(nsc['fedora'].lastUpdated, ts)
  83. self.rsrc.set(nsc['fedora'].lastUpdatedBy, Literal('BypassAdmin'))
  84. for s, p, o in g:
  85. self.ds.add((s, p, o))
  86. def create_rsrc(self, g):
  87. '''
  88. See base_rdf_strategy.create_rsrc.
  89. '''
  90. # @TODO Use gunicorn to get request timestamp.
  91. ts = Literal(arrow.utcnow(), datatype=XSD.dateTime)
  92. self.rsrc.set(nsc['fedora'].created, ts)
  93. self.rsrc.set(nsc['fedora'].createdBy, Literal('BypassAdmin'))
  94. cksum = Digest.rdf_cksum(self.rsrc.graph)
  95. self.rsrc.set(nsc['premis'].hasMessageDigest,
  96. URIRef('urn:sha1:{}'.format(cksum)))
  97. for s, p, o in g:
  98. self.ds.add((s, p, o))
  99. def patch_rsrc(self, data):
  100. '''
  101. Perform a SPARQL UPDATE on a resource.
  102. '''
  103. # @TODO Use gunicorn to get request timestamp.
  104. ts = Literal(arrow.utcnow(), datatype=XSD.dateTime)
  105. q = Translator.localize_string(data).replace(
  106. '<>', self.rsrc.identifier.n3())
  107. self.rsrc.set(nsc['fedora'].lastUpdated, ts)
  108. self.rsrc.set(nsc['fedora'].lastUpdatedBy, Literal('BypassAdmin'))
  109. self.ds.update(q)
  110. def delete_rsrc(self, inbound=False):
  111. '''
  112. Delete a resource. If `inbound` is specified, delete all inbound
  113. relationships as well.
  114. '''
  115. print('Removing resource {}.'.format(self.rsrc.identifier))
  116. self.rsrc.remove(Variable('p'))
  117. if inbound:
  118. self.ds.remove((Variable('s'), Variable('p'), self.rsrc.identifier))
  119. ## PROTECTED METHODS ##
  120. def _unique_value(self, p):
  121. '''
  122. Use this to retrieve a single value knowing that there SHOULD be only
  123. one (e.g. `skos:prefLabel`), If more than one is found, raise an
  124. exception.
  125. @param rdflib.Resource rsrc The resource to extract value from.
  126. @param rdflib.term.URIRef p The predicate to serach for.
  127. @throw ValueError if more than one value is found.
  128. '''
  129. values = self.rsrc[p]
  130. value = next(values)
  131. try:
  132. next(values)
  133. except StopIteration:
  134. return value
  135. # If the second next() did not raise a StopIteration, something is
  136. # wrong.
  137. raise ValueError('Predicate {} should be single valued. Found: {}.'\
  138. .format(set(values)))