simple_layout.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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_layouts.rdf.base_rdf_layout import BaseRdfLayout
  10. from lakesuperior.util.digest import Digest
  11. class SimpleLayout(BaseRdfLayout):
  12. '''
  13. This is the simplest layout.
  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_layout.headers.
  23. '''
  24. headers = {
  25. 'Link' : [],
  26. }
  27. # @NOTE: Easy with these one-by-one picks. Each one of them is a call
  28. # to the triplestore.
  29. digest = self.rsrc.value(nsc['premis'].hasMessageDigest)
  30. if digest:
  31. etag = digest.identifier.split(':')[-1]
  32. headers['ETag'] = 'W/"{}"'.format(etag),
  33. last_updated_term = self.rsrc.value(nsc['fedora'].lastUpdated)
  34. if last_updated_term:
  35. headers['Last-Modified'] = arrow.get(last_updated_term)\
  36. .format('ddd, D MMM YYYY HH:mm:ss Z')
  37. return headers
  38. def out_rsrc(self, srv_mgd=True, inbound=False, embed_children=False):
  39. '''
  40. See base_rdf_layout.out_rsrc.
  41. '''
  42. im_rsrc = self.extract_rsrc(inbound=inbound)
  43. im_rsrc.remove(nsc['premis'].hasMessageDigest)
  44. return im_rsrc
  45. def ask_rsrc_exists(self, rsrc=None):
  46. '''
  47. See base_rdf_layout.ask_rsrc_exists.
  48. '''
  49. if not rsrc:
  50. if self.rsrc is not None:
  51. rsrc = self.rsrc
  52. else:
  53. return False
  54. self._logger.info('Searching for resource: {}'
  55. .format(rsrc.identifier))
  56. return (rsrc.identifier, Variable('p'), Variable('o')) in self.ds
  57. def create_or_replace_rsrc(self, g):
  58. '''
  59. See base_rdf_layout.create_or_replace_rsrc.
  60. '''
  61. # @TODO Use gunicorn to get request timestamp.
  62. ts = Literal(arrow.utcnow(), datatype=XSD.dateTime)
  63. if self.ask_rsrc_exists():
  64. self._logger.info(
  65. 'Resource {} exists. Removing all outbound triples.'
  66. .format(self.rsrc.identifier))
  67. # Delete all triples but keep creation date and creator.
  68. created = self.rsrc.value(nsc['fedora'].created)
  69. created_by = self.rsrc.value(nsc['fedora'].createdBy)
  70. self.delete_rsrc()
  71. else:
  72. created = ts
  73. created_by = Literal('BypassAdmin')
  74. self.rsrc.set(nsc['fedora'].created, created)
  75. self.rsrc.set(nsc['fedora'].createdBy, created_by)
  76. self.rsrc.set(nsc['fedora'].lastUpdated, ts)
  77. self.rsrc.set(nsc['fedora'].lastUpdatedBy, Literal('BypassAdmin'))
  78. for s, p, o in g:
  79. self.ds.add((s, p, o))
  80. def create_rsrc(self, g):
  81. '''
  82. See base_rdf_layout.create_rsrc.
  83. '''
  84. # @TODO Use gunicorn to get request timestamp.
  85. ts = Literal(arrow.utcnow(), datatype=XSD.dateTime)
  86. self.rsrc.set(nsc['fedora'].created, ts)
  87. self.rsrc.set(nsc['fedora'].createdBy, Literal('BypassAdmin'))
  88. cksum = Digest.rdf_cksum(self.rsrc.graph)
  89. self.rsrc.set(nsc['premis'].hasMessageDigest,
  90. URIRef('urn:sha1:{}'.format(cksum)))
  91. for s, p, o in g:
  92. self.ds.add((s, p, o))
  93. def patch_rsrc(self, data):
  94. '''
  95. Perform a SPARQL UPDATE on a resource.
  96. '''
  97. # @TODO Use gunicorn to get request timestamp.
  98. ts = Literal(arrow.utcnow(), datatype=XSD.dateTime)
  99. q = Translator.localize_string(data).replace(
  100. '<>', self.rsrc.identifier.n3())
  101. self.rsrc.set(nsc['fedora'].lastUpdated, ts)
  102. self.rsrc.set(nsc['fedora'].lastUpdatedBy, Literal('BypassAdmin'))
  103. self.ds.update(q)
  104. def delete_rsrc(self, inbound=False):
  105. '''
  106. Delete a resource. If `inbound` is specified, delete all inbound
  107. relationships as well.
  108. '''
  109. print('Removing resource {}.'.format(self.rsrc.identifier))
  110. self.rsrc.remove(Variable('p'))
  111. if inbound:
  112. self.ds.remove((Variable('s'), Variable('p'), self.rsrc.identifier))
  113. ## PROTECTED METHODS ##
  114. def _unique_value(self, p):
  115. '''
  116. Use this to retrieve a single value knowing that there SHOULD be only
  117. one (e.g. `skos:prefLabel`), If more than one is found, raise an
  118. exception.
  119. @param rdflib.Resource rsrc The resource to extract value from.
  120. @param rdflib.term.URIRef p The predicate to serach for.
  121. @throw ValueError if more than one value is found.
  122. '''
  123. values = self.rsrc[p]
  124. value = next(values)
  125. try:
  126. next(values)
  127. except StopIteration:
  128. return value
  129. # If the second next() did not raise a StopIteration, something is
  130. # wrong.
  131. raise ValueError('Predicate {} should be single valued. Found: {}.'\
  132. .format(set(values)))