ldp_factory.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. import logging
  2. from pprint import pformat
  3. from uuid import uuid4
  4. import rdflib
  5. from flask import current_app, g
  6. from rdflib import Graph
  7. from rdflib.resource import Resource
  8. from rdflib.namespace import RDF
  9. from lakesuperior import model
  10. from lakesuperior.model.generic_resource import PathSegment
  11. from lakesuperior.dictionaries.namespaces import ns_collection as nsc
  12. from lakesuperior.exceptions import (
  13. IncompatibleLdpTypeError, InvalidResourceError, ResourceExistsError,
  14. ResourceNotExistsError)
  15. class LdpFactory:
  16. '''
  17. Generate LDP instances.
  18. The instance classes are based on provided client data or on stored data.
  19. '''
  20. LDP_NR_TYPE = nsc['ldp'].NonRDFSource
  21. LDP_RS_TYPE = nsc['ldp'].RDFSource
  22. _logger = logging.getLogger(__name__)
  23. @staticmethod
  24. def new_container(uid):
  25. if not uid:
  26. raise InvalidResourceError(uid)
  27. if current_app.rdfly.ask_rsrc_exists(uid):
  28. raise ResourceExistsError(uid)
  29. rsrc = model.ldp_rs.Ldpc(
  30. uid, provided_imr=Resource(Graph(), nsc['fcres'][uid]))
  31. return rsrc
  32. @staticmethod
  33. def from_stored(uid, repr_opts={}, **kwargs):
  34. '''
  35. Create an instance for retrieval purposes.
  36. This factory method creates and returns an instance of an LDPR subclass
  37. based on information that needs to be queried from the underlying
  38. graph store.
  39. N.B. The resource must exist.
  40. @param uid UID of the instance.
  41. '''
  42. #__class__._logger.info('Retrieving stored resource: {}'.format(uid))
  43. imr_urn = nsc['fcres'][uid]
  44. rsrc_meta = current_app.rdfly.get_metadata(uid)
  45. #__class__._logger.debug('Extracted metadata: {}'.format(
  46. # pformat(set(rsrc_meta.graph))))
  47. rdf_types = set(rsrc_meta.graph[imr_urn : RDF.type])
  48. if __class__.LDP_NR_TYPE in rdf_types:
  49. __class__._logger.info('Resource is a LDP-NR.')
  50. rsrc = model.ldp_nr.LdpNr(uid, repr_opts, **kwargs)
  51. elif __class__.LDP_RS_TYPE in rdf_types:
  52. __class__._logger.info('Resource is a LDP-RS.')
  53. rsrc = model.ldp_rs.LdpRs(uid, repr_opts, **kwargs)
  54. elif nsc['fcsystem']['PathSegment'] in rdf_types:
  55. return PathSegment(uid)
  56. else:
  57. raise ResourceNotExistsError(uid)
  58. # Sneak in the already extracted metadata to save a query.
  59. rsrc._metadata = rsrc_meta
  60. return rsrc
  61. @staticmethod
  62. def from_provided(uid, content_length, mimetype, stream, **kwargs):
  63. '''
  64. Determine LDP type from request content.
  65. @param uid (string) UID of the resource to be created or updated.
  66. @param content_length (int) The provided content length.
  67. @param mimetype (string) The provided content MIME type.
  68. @param stream (IOStream) The provided data stream. This can be RDF or
  69. non-RDF content.
  70. '''
  71. urn = nsc['fcres'][uid]
  72. logger = __class__._logger
  73. if not content_length:
  74. # Create empty LDPC.
  75. logger.info('No data received in request. '
  76. 'Creating empty container.')
  77. inst = model.ldp_rs.Ldpc(
  78. uid, provided_imr=Resource(Graph(), urn), **kwargs)
  79. elif __class__.is_rdf_parsable(mimetype):
  80. # Create container and populate it with provided RDF data.
  81. input_rdf = stream.read()
  82. provided_gr = Graph().parse(data=input_rdf,
  83. format=mimetype, publicID=urn)
  84. #logger.debug('Provided graph: {}'.format(
  85. # pformat(set(provided_gr))))
  86. local_gr = g.tbox.localize_graph(provided_gr)
  87. #logger.debug('Parsed local graph: {}'.format(
  88. # pformat(set(local_gr))))
  89. provided_imr = Resource(local_gr, urn)
  90. # Determine whether it is a basic, direct or indirect container.
  91. Ldpr = model.ldpr.Ldpr
  92. if Ldpr.MBR_RSRC_URI in local_gr.predicates() and \
  93. Ldpr.MBR_REL_URI in local_gr.predicates():
  94. if Ldpr.INS_CNT_REL_URI in local_gr.predicates():
  95. cls = model.ldp_rs.LdpIc
  96. else:
  97. cls = model.ldp_rs.LdpDc
  98. else:
  99. cls = model.ldp_rs.Ldpc
  100. inst = cls(uid, provided_imr=provided_imr, **kwargs)
  101. # Make sure we are not updating an LDP-RS with an LDP-NR.
  102. if inst.is_stored and __class__.LDP_NR_TYPE in inst.ldp_types:
  103. raise IncompatibleLdpTypeError(uid, mimetype)
  104. if kwargs.get('handling', 'strict') != 'none':
  105. inst._check_mgd_terms(inst.provided_imr.graph)
  106. else:
  107. # Create a LDP-NR and equip it with the binary file provided.
  108. provided_imr = Resource(Graph(), urn)
  109. inst = model.ldp_nr.LdpNr(uid, stream=stream, mimetype=mimetype,
  110. provided_imr=provided_imr, **kwargs)
  111. # Make sure we are not updating an LDP-NR with an LDP-RS.
  112. if inst.is_stored and __class__.LDP_RS_TYPE in inst.ldp_types:
  113. raise IncompatibleLdpTypeError(uid, mimetype)
  114. logger.info('Creating resource of type: {}'.format(
  115. inst.__class__.__name__))
  116. try:
  117. types = inst.types
  118. except:
  119. types = set()
  120. return inst
  121. @staticmethod
  122. def is_rdf_parsable(mimetype):
  123. '''
  124. Checks whether a MIME type support RDF parsing by a RDFLib plugin.
  125. @param mimetype (string) MIME type to check.
  126. '''
  127. try:
  128. rdflib.plugin.get(mimetype, rdflib.parser.Parser)
  129. except rdflib.plugin.PluginException:
  130. return False
  131. else:
  132. return True
  133. @staticmethod
  134. def is_rdf_serializable(mimetype):
  135. '''
  136. Checks whether a MIME type support RDF serialization by a RDFLib plugin
  137. @param mimetype (string) MIME type to check.
  138. '''
  139. try:
  140. rdflib.plugin.get(mimetype, rdflib.serializer.Serializer)
  141. except rdflib.plugin.PluginException:
  142. return False
  143. else:
  144. return True
  145. @staticmethod
  146. def mint_uid(parent_uid, path=None):
  147. '''
  148. Mint a new resource UID based on client directives.
  149. This method takes a parent ID and a tentative path and returns an LDP
  150. resource UID.
  151. This may raise an exception resulting in a 404 if the parent is not
  152. found or a 409 if the parent is not a valid container.
  153. @param parent_uid (string) UID of the parent resource. It must be an
  154. existing LDPC.
  155. @param path (string) path to the resource, relative to the parent.
  156. @return string The confirmed resource UID. This may be different from
  157. what has been indicated.
  158. '''
  159. def split_if_legacy(uid):
  160. if current_app.config['store']['ldp_rs']['legacy_ptree_split']:
  161. uid = g.tbox.split_uuid(uid)
  162. return uid
  163. # Shortcut!
  164. if not path and parent_uid == '':
  165. uid = split_if_legacy(str(uuid4()))
  166. return uid
  167. parent = LdpFactory.from_stored(parent_uid,
  168. repr_opts={'incl_children' : False})
  169. # Set prefix.
  170. if parent_uid:
  171. if nsc['ldp'].Container not in parent.types:
  172. raise InvalidResourceError(parent_uid,
  173. 'Parent {} is not a container.')
  174. pfx = parent_uid + '/'
  175. else:
  176. pfx = ''
  177. # Create candidate UID and validate.
  178. if path:
  179. cnd_uid = pfx + path
  180. if current_app.rdfly.ask_rsrc_exists(cnd_uid):
  181. uid = pfx + split_if_legacy(str(uuid4()))
  182. else:
  183. uid = cnd_uid
  184. else:
  185. uid = pfx + split_if_legacy(str(uuid4()))
  186. return uid