ldp_factory.py 7.5 KB

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