Browse Source
* JSON-LD Context generator Base pull, service, interface and class. Needs testing. DCS should be solved already * Missing an @ * Wrong Cache backend service injected * make sure the bundle has mapping * Testing routes to make debugging easier * phpstorm, leave my indentation alone! * phpcs for test controller * Changes, lots of them * Exceptions thrown all around (and documented * “Try/Catch” where relevant * Logger channel for ISLANDORA, useful for all CLAW * Exceptions are being cached * Naive field types of json-ld term definitions for context. Kinda poor mans rdf map for fields * Docs, docs. * Route Controller now responds with application/ld+json, means don’t wait for HTML! * Concerns addressed TODO: need new tests. * Web tests! Don’t run via UI (buggy) https://www.drupal.org/node/2745123 Do this ```Shell sudo -u www-data php core/scripts/run-tests.sh --verbose --class "Drupal\islandora\Tests\Web\JsonldContextGeneratorWebTest" ``` * Coding standards * Coding standards and Cache Now caching happens on the response and on the method. Best of both worlds. ```Shell curl -i http://localhost:8000/fedora_resource_context/rdf_source HTTP/1.1 200 OK Date: Tue, 21 Mar 2017 19:19:03 GMT Server: Apache/2.4.18 (Ubuntu) Cache-Control: must-revalidate, no-cache, private X-Powered-By: Islandora CLAW API X-Drupal-Dynamic-Cache: MISS X-UA-Compatible: IE=edge Content-language: en X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN Expires: Sun, 19 Nov 1978 05:00:00 GMT X-Generator: Drupal 8 (https://www.drupal.org) X-Debug-Token: 7d33c2 X-Debug-Token-Link: /admin/reports/profiler/view/7d33c2 X-Drupal-Cache: HIT Content-Length: 229 Content-Type: application/ld+json {"@context":{"schema":"http://schema.org/","schema:dateModified":{"@type ":"xsd:dateTime"},"schema:dateCreated":{"@type":"xsd:dateTime"},"fedora" :"http://fedora.info/definitions/v4/repository#","fedora:hasParent":{"@t ype":"@id"}}} ```` and after cache clear (or changing user permissions or even an entity type def associated to the requested rdfmapping) ```Shell HTTP/1.1 200 OK Date: Tue, 21 Mar 2017 19:20:49 GMT Server: Apache/2.4.18 (Ubuntu) Cache-Control: must-revalidate, no-cache, private X-Powered-By: Islandora CLAW API X-Drupal-Dynamic-Cache: MISS X-UA-Compatible: IE=edge Content-language: en X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN Expires: Sun, 19 Nov 1978 05:00:00 GMT X-Generator: Drupal 8 (https://www.drupal.org) X-Debug-Token: acc399 X-Debug-Token-Link: /admin/reports/profiler/view/acc399 X-Drupal-Cache: MISS Content-Length: 229 Content-Type: application/ld+json ```` * Short notion array.. should be named “bracket structure…” * We should run tests on our own server... * lets try with 127.0.0.1 * testing travis changes (#1) * fixes not working Drupal/drush integration on Travis-CI * Kernel tests * 400 is 1 less than 401 * restore notifications * Fixes type in TODO * Jared rocks * Debug statement not needed Was not outputting anyway * Namespace change Addressing @dhlamb namespace changepull/756/head
Diego Pino Navarro
8 years ago
committed by
dannylamb
11 changed files with 867 additions and 21 deletions
@ -0,0 +1,88 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\islandora\Controller; |
||||||
|
|
||||||
|
use Drupal\Core\Cache\CacheableJsonResponse; |
||||||
|
use Drupal\Core\Controller\ControllerBase; |
||||||
|
use Drupal\islandora\JsonldContextGenerator\JsonldContextGeneratorInterface; |
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface; |
||||||
|
use Symfony\Component\HttpFoundation\Request; |
||||||
|
use Symfony\Component\HttpFoundation\Response; |
||||||
|
use Drupal\rdf\Entity\RdfMapping; |
||||||
|
use Drupal\Core\Cache\Cache; |
||||||
|
use Drupal\Core\Cache\CacheableMetadata; |
||||||
|
|
||||||
|
/** |
||||||
|
* Class FedoraResourceJsonLdContextController. |
||||||
|
* |
||||||
|
* @package Drupal\islandora\Controller |
||||||
|
*/ |
||||||
|
class FedoraResourceJsonLdContextController extends ControllerBase { |
||||||
|
|
||||||
|
/** |
||||||
|
* Injected JsonldContextGenerator. |
||||||
|
* |
||||||
|
* @var \Drupal\islandora\JsonldContextGenerator\JsonldContextGeneratorInterface |
||||||
|
*/ |
||||||
|
private $jsonldContextGenerator; |
||||||
|
|
||||||
|
/** |
||||||
|
* FedoraResourceJsonLdContextController constructor. |
||||||
|
* |
||||||
|
* @param \Drupal\islandora\JsonldContextGenerator\JsonldContextGeneratorInterface $jsonld_context_generator |
||||||
|
* Injected JsonldContextGenerator. |
||||||
|
*/ |
||||||
|
public function __construct(JsonldContextGeneratorInterface $jsonld_context_generator) { |
||||||
|
$this->jsonldContextGenerator = $jsonld_context_generator; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Controller's create method for dependecy injection. |
||||||
|
* |
||||||
|
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container |
||||||
|
* The App Container. |
||||||
|
* |
||||||
|
* @return static |
||||||
|
* An instance of our islandora.jsonldcontextgenerator service. |
||||||
|
*/ |
||||||
|
public static function create(ContainerInterface $container) { |
||||||
|
return new static($container->get('islandora.jsonldcontextgenerator')); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns an JSON-LD Context for a fedora_resource bundle. |
||||||
|
* |
||||||
|
* @param string $bundle |
||||||
|
* Route argument, a bundle. |
||||||
|
* @param \Symfony\Component\HttpFoundation\Request $request |
||||||
|
* The Symfony Http Request. |
||||||
|
* |
||||||
|
* @return \Symfony\Component\HttpFoundation\Response |
||||||
|
* An Http response. |
||||||
|
*/ |
||||||
|
public function content($bundle, Request $request) { |
||||||
|
|
||||||
|
// TODO: expose cached/not cached through |
||||||
|
// more varied HTTP response codes. |
||||||
|
try { |
||||||
|
$context = $this->jsonldContextGenerator->getContext('fedora_resource.' . $bundle); |
||||||
|
$response = new CacheableJsonResponse(json_decode($context), 200); |
||||||
|
$response->setEncodingOptions(JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK); |
||||||
|
$response->headers->set('X-Powered-By', 'Islandora CLAW API'); |
||||||
|
$response->headers->set('Content-Type', 'application/ld+json'); |
||||||
|
|
||||||
|
// For now deal with Cache dependencies manually. |
||||||
|
$meta = new CacheableMetadata(); |
||||||
|
$meta->setCacheContexts(['user.permissions', 'ip', 'url']); |
||||||
|
$meta->setCacheTags(RdfMapping::load('fedora_resource.' . $bundle)->getCacheTags()); |
||||||
|
$meta->setCacheMaxAge(Cache::PERMANENT); |
||||||
|
$response->addCacheableDependency($meta); |
||||||
|
} |
||||||
|
catch (\Exception $e) { |
||||||
|
$response = new Response($e->getMessage(), 400); |
||||||
|
} |
||||||
|
|
||||||
|
return $response; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,424 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\islandora\JsonldContextGenerator; |
||||||
|
|
||||||
|
use Drupal\Core\Entity\EntityFieldManagerInterface; |
||||||
|
use Drupal\Core\Entity\EntityTypeManagerInterface; |
||||||
|
use Drupal\Core\Entity\EntityTypeBundleInfoInterface; |
||||||
|
use Drupal\Core\Cache\Cache; |
||||||
|
use Drupal\Core\Cache\CacheBackendInterface; |
||||||
|
use Drupal\Core\Field\FieldDefinitionInterface; |
||||||
|
use Drupal\rdf\RdfMappingInterface; |
||||||
|
use Drupal\rdf\Entity\RdfMapping; |
||||||
|
use Psr\Log\LoggerInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* A reliable JSON-LD @Context generation class. |
||||||
|
* |
||||||
|
* Class JsonldContextGenerator. |
||||||
|
* |
||||||
|
* @package Drupal\islandora\JsonldContextGenerator |
||||||
|
*/ |
||||||
|
class JsonldContextGenerator implements JsonldContextGeneratorInterface { |
||||||
|
|
||||||
|
/** |
||||||
|
* Constant Naming convention used to prefix name cache bins($cid) |
||||||
|
*/ |
||||||
|
const CACHE_BASE_CID = 'islandora:jsonld:context'; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Injected EntityFieldManager. |
||||||
|
* |
||||||
|
* @var \Drupal\Core\Entity\EntityFieldManagerInterface |
||||||
|
*/ |
||||||
|
protected $entityFieldManager = NULL; |
||||||
|
|
||||||
|
/** |
||||||
|
* Injected EntityTypeManager. |
||||||
|
* |
||||||
|
* @var \Drupal\Core\Entity\EntityTypeManagerInterface |
||||||
|
*/ |
||||||
|
protected $entityTypeManager = NULL; |
||||||
|
|
||||||
|
/** |
||||||
|
* Injected EntityTypeBundle. |
||||||
|
* |
||||||
|
* @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface |
||||||
|
*/ |
||||||
|
protected $bundleInfo = NULL; |
||||||
|
|
||||||
|
/** |
||||||
|
* Injected Cache implementation. |
||||||
|
* |
||||||
|
* @var \Drupal\Core\Cache\CacheBackendInterface |
||||||
|
*/ |
||||||
|
protected $cache; |
||||||
|
|
||||||
|
/** |
||||||
|
* Injected Logger Interface. |
||||||
|
* |
||||||
|
* @var \Psr\Log\LoggerInterface |
||||||
|
* A logger instance. |
||||||
|
*/ |
||||||
|
protected $logger; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructs a JsonldContextGenerator object. |
||||||
|
* |
||||||
|
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager |
||||||
|
* The entity manager. |
||||||
|
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info |
||||||
|
* The language manager. |
||||||
|
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_manager |
||||||
|
* The Entity Type Manager. |
||||||
|
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend |
||||||
|
* Caching Backend. |
||||||
|
* @param \Psr\Log\LoggerInterface $logger_channel |
||||||
|
* Our Logging Channel. |
||||||
|
*/ |
||||||
|
public function __construct(EntityFieldManagerInterface $entity_field_manager, EntityTypeBundleInfoInterface $bundle_info, EntityTypeManagerInterface $entity_manager, CacheBackendInterface $cache_backend, LoggerInterface $logger_channel) { |
||||||
|
$this->entityFieldManager = $entity_field_manager; |
||||||
|
$this->entityTypeManager = $entity_manager; |
||||||
|
$this->bundleInfo = $bundle_info; |
||||||
|
$this->cache = $cache_backend; |
||||||
|
$this->logger = $logger_channel; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getContext($ids = 'fedora_resource.rdf_source') { |
||||||
|
$cid = JsonldContextGenerator::CACHE_BASE_CID . $ids; |
||||||
|
$cache = $this->cache->get($cid); |
||||||
|
$data = ''; |
||||||
|
if (!$cache) { |
||||||
|
$rdfMapping = RdfMapping::load($ids); |
||||||
|
// Our whole chain of exceptions will never happen |
||||||
|
// because RdfMapping:load returns NULL on non existance |
||||||
|
// Which forces me to check for it |
||||||
|
// and don't even call writeCache on missing |
||||||
|
// Solution, throw also one here. |
||||||
|
if ($rdfMapping) { |
||||||
|
$data = $this->writeCache($rdfMapping, $cid); |
||||||
|
} |
||||||
|
else { |
||||||
|
$msg = t("Can't generate JSON-LD Context for @ids without RDF Mapping present.", |
||||||
|
['@ids' => $ids]); |
||||||
|
$this->logger->warning("@msg", |
||||||
|
[ |
||||||
|
'@msg' => $msg, |
||||||
|
]); |
||||||
|
throw new \Exception($msg); |
||||||
|
} |
||||||
|
} |
||||||
|
else { |
||||||
|
$data = $cache->data; |
||||||
|
} |
||||||
|
return $data; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function generateContext(RdfMappingInterface $rdfMapping) { |
||||||
|
// TODO: we will need to use \Drupal\Core\Field\FieldDefinitionInterface |
||||||
|
// a lot to be able to create/frame/discern drupal bundles based on JSON-LD |
||||||
|
// So keep an eye on that definition. |
||||||
|
$allRdfNameSpaces = rdf_get_namespaces(); |
||||||
|
|
||||||
|
// This one will become our return value. |
||||||
|
$jsonLdContextArray['@context'] = []; |
||||||
|
|
||||||
|
// Temporary array to keep track of our used namespaces and props. |
||||||
|
$theAccumulator = []; |
||||||
|
|
||||||
|
$bundle_rdf_mappings = $rdfMapping->getPreparedBundleMapping(); |
||||||
|
$drupal_types = $this->entityBundleIdsSplitter($rdfMapping->id()); |
||||||
|
$entity_type_id = $drupal_types['entityTypeId']; |
||||||
|
$bundle = $drupal_types['bundleId']; |
||||||
|
// If we don't have rdf:type(s) for this bundle then it makes little |
||||||
|
// sense to continue. |
||||||
|
// This only generates an Exception if there is an |
||||||
|
// rdfmapping object but has no rdf:type. |
||||||
|
if (empty($bundle_rdf_mappings['types'])) { |
||||||
|
$msg = t("Can't generate JSON-LD Context without at least one rdf:type for Entity type @entity_type, Bundle @bundle_name combo.", |
||||||
|
['@entity_type' => $entity_type_id, ' @bundle_name' => $bundle]); |
||||||
|
$this->logger->warning("@msg", |
||||||
|
[ |
||||||
|
'@msg' => $msg, |
||||||
|
]); |
||||||
|
throw new \Exception($msg); |
||||||
|
} |
||||||
|
|
||||||
|
/* We have a lot of assumptions here (rdf module is strange) |
||||||
|
a) xsd and other utility namespaces are in place |
||||||
|
b) the user knows what/how rdf mapping works and does it right |
||||||
|
c) that if a field's mapping_type is "rel" or "rev" and datatype is |
||||||
|
not defined, then '@type' is uncertain. |
||||||
|
d) that mapping back and forward is 1 to 1. |
||||||
|
Drupal allows multiple fields to be mapped to a same rdf prop |
||||||
|
but that does not scale back. If drupal gets an input with a list |
||||||
|
of values for a given property, we would never know in which Drupal |
||||||
|
fields we should put those values. it's the many to one, |
||||||
|
one to many reduction problem made worst by the abstraction of |
||||||
|
fields being containers of mappings and not rdf properties. */ |
||||||
|
// Only care for those mappings that point to bundled or base fields. |
||||||
|
// First our bundled fields. |
||||||
|
foreach ($this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle) as $bundleFieldName => $fieldDefinition) { |
||||||
|
$field_context = $this->getFieldsRdf($rdfMapping, $bundleFieldName, $fieldDefinition, $allRdfNameSpaces); |
||||||
|
$theAccumulator = array_merge($field_context, $theAccumulator); |
||||||
|
} |
||||||
|
// And then our Base fields. |
||||||
|
foreach ($this->entityFieldManager->getBaseFieldDefinitions($entity_type_id) as $baseFieldName => $fieldDefinition) { |
||||||
|
$field_context = $this->getFieldsRdf($rdfMapping, $baseFieldName, $fieldDefinition, $allRdfNameSpaces); |
||||||
|
$theAccumulator = array_merge($field_context, $theAccumulator); |
||||||
|
} |
||||||
|
$theAccumulator = array_filter($theAccumulator); |
||||||
|
$jsonLdContextArray['@context'] = $theAccumulator; |
||||||
|
return json_encode($jsonLdContextArray, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the correct piece of @context for a given entity field. |
||||||
|
* |
||||||
|
* @param \Drupal\rdf\RdfMappingInterface $rdfMapping |
||||||
|
* Rdf mapping object. |
||||||
|
* @param string $field_name |
||||||
|
* The name of the field. |
||||||
|
* @param \Drupal\Core\Field\FieldDefinitionInterface $fieldDefinition |
||||||
|
* The definition of the field. |
||||||
|
* @param array $allRdfNameSpaces |
||||||
|
* Every RDF prefixed namespace in this Drupal. |
||||||
|
* |
||||||
|
* @return array |
||||||
|
* Piece of JSON-LD context that supports this field |
||||||
|
*/ |
||||||
|
private function getFieldsRdf(RdfMappingInterface $rdfMapping, $field_name, FieldDefinitionInterface $fieldDefinition, array $allRdfNameSpaces) { |
||||||
|
$termDefinition = []; |
||||||
|
$fieldContextFragment = []; |
||||||
|
$fieldRDFMapping = $rdfMapping->getPreparedFieldMapping($field_name); |
||||||
|
if (!empty($fieldRDFMapping)) { |
||||||
|
// If one ore more properties, all will share same datatype so |
||||||
|
// get that before iterating. |
||||||
|
// First get our defaults, no-user or config based input. |
||||||
|
$default_field_term_mapping = $this->getTermContextFromField($fieldDefinition->getType()); |
||||||
|
|
||||||
|
// Now we start overriding from config entity defined mappings. |
||||||
|
// Assume all non defined mapping types as "property". |
||||||
|
$reltype = isset($fieldRDFMapping['mapping_type']) ? $fieldRDFMapping['mapping_type'] : 'property'; |
||||||
|
|
||||||
|
if (isset($fieldRDFMapping['datatype']) && ($reltype == 'property')) { |
||||||
|
$termDefinition = ['@type' => $fieldRDFMapping['datatype']]; |
||||||
|
} |
||||||
|
if (!isset($fieldRDFMapping['datatype']) && ($reltype != 'property')) { |
||||||
|
$termDefinition = ['@type' => '@id']; |
||||||
|
} |
||||||
|
|
||||||
|
// This should respect user provided mapping and fill rest with defaults. |
||||||
|
$termDefinition = $termDefinition + $default_field_term_mapping; |
||||||
|
|
||||||
|
// Now iterate over all properties for this field |
||||||
|
// trying to parse them as compact IRI. |
||||||
|
foreach ($fieldRDFMapping['properties'] as $property) { |
||||||
|
$compactedDefinition = $this->parseCompactedIri($property); |
||||||
|
if ($compactedDefinition['prefix'] != NULL) { |
||||||
|
// Check if the namespace prefix exists. |
||||||
|
if (array_key_exists($compactedDefinition['prefix'], $allRdfNameSpaces)) { |
||||||
|
// Just overwrite as many times as needed, |
||||||
|
// still faster than checking if |
||||||
|
// it's there in the first place. |
||||||
|
$fieldContextFragment[$compactedDefinition['prefix']] = $allRdfNameSpaces[$compactedDefinition['prefix']]; |
||||||
|
$fieldContextFragment[$property] = $termDefinition; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $fieldContextFragment; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Writes JSON-LD @context cache per Entity_type bundle combo. |
||||||
|
* |
||||||
|
* @param \Drupal\rdf\RdfMappingInterface $rdfMapping |
||||||
|
* Rdf mapping object. |
||||||
|
* @param string $cid |
||||||
|
* Name of the cache bin to use. |
||||||
|
* |
||||||
|
* @return string |
||||||
|
* A json encoded string for the processed JSON-LD @context |
||||||
|
*/ |
||||||
|
protected function writeCache(RdfMappingInterface $rdfMapping, $cid) { |
||||||
|
|
||||||
|
// This is how an empty json encoded @context looks like. |
||||||
|
$data = json_encode(['@context' => ''], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); |
||||||
|
try { |
||||||
|
$data = $this->generateContext($rdfMapping); |
||||||
|
$this->cache->set($cid, $data, Cache::PERMANENT, $rdfMapping->getCacheTagsToInvalidate()); |
||||||
|
} |
||||||
|
catch (\Exception $e) { |
||||||
|
$this->logger->warning("@msg", |
||||||
|
[ |
||||||
|
'@msg' => $e->getMessage(), |
||||||
|
]); |
||||||
|
} |
||||||
|
|
||||||
|
return $data; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Absurdly simple exploder for a joint entityType and Bundle ids string. |
||||||
|
* |
||||||
|
* @param string $ids |
||||||
|
* A string with containing entity id and bundle joined by a dot. |
||||||
|
* |
||||||
|
* @return array |
||||||
|
* And array with the entity type and the bundle id |
||||||
|
*/ |
||||||
|
protected function entityBundleIdsSplitter($ids) { |
||||||
|
list($entity_type_id, $bundle_id) = explode(".", $ids, 2); |
||||||
|
return ['entityTypeId' => $entity_type_id, 'bundleId' => $bundle_id]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Parses and IRI, checks if it is complaint with compacted IRI definition. |
||||||
|
* |
||||||
|
* Assumes this notion of compact IRI/similar to CURIE |
||||||
|
* http://json-ld.org/spec/ED/json-ld-syntax/20120522/#dfn-prefix. |
||||||
|
* |
||||||
|
* @param string $iri |
||||||
|
* IRIs are strings. |
||||||
|
* |
||||||
|
* @return array |
||||||
|
* If $iri is a compacted iri, prefix and term as separate |
||||||
|
* array members, if not, unmodified $iri in term position |
||||||
|
* and null prefix. |
||||||
|
*/ |
||||||
|
protected function parseCompactedIri($iri) { |
||||||
|
// As naive as it gets. |
||||||
|
list($prefix, $rest) = array_pad(explode(":", $iri, 2), 2, ''); |
||||||
|
if ((substr($rest, 0, 2) == "//") || ($prefix == $iri)) { |
||||||
|
// Means this was never a compacted IRI. |
||||||
|
return ['prefix' => NULL, 'term' => $iri]; |
||||||
|
} |
||||||
|
return ['prefix' => $prefix, 'term' => $rest]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Naive approach on Drupal field to JSON-LD type mapping. |
||||||
|
* |
||||||
|
* TODO: Would be fine to have this definitions in an |
||||||
|
* configEntity way in the future. |
||||||
|
* |
||||||
|
* @param string $field_type |
||||||
|
* As provided by \Drupal\Core\Field\FieldDefinitionInterface::getType(). |
||||||
|
* |
||||||
|
* @return array |
||||||
|
* A json-ld term definition if there is a match |
||||||
|
* or array("@type" => "xsd:string") in case of no match. |
||||||
|
*/ |
||||||
|
protected function getTermContextFromField($field_type) { |
||||||
|
// Be aware that drupal field definitions can be complex. |
||||||
|
// e.g text_with_summary has a text, a summary, a number of lines, etc |
||||||
|
// we are only dealing with the resulting ->value() of all this separate |
||||||
|
// pieces and mapping only that as a whole. |
||||||
|
// Default mapping to return in case no $field_type matches |
||||||
|
// field_mappings array keys. |
||||||
|
$default_mapping = [ |
||||||
|
"@type" => "xsd:string", |
||||||
|
]; |
||||||
|
|
||||||
|
$field_mappings = [ |
||||||
|
"comment" => [ |
||||||
|
"@type" => "xsd:string", |
||||||
|
], |
||||||
|
"datetime" => [ |
||||||
|
"@type" => "xsd:dateTime", |
||||||
|
], |
||||||
|
"file" => [ |
||||||
|
"@type" => "@id", |
||||||
|
], |
||||||
|
"image" => [ |
||||||
|
"@type" => "@id", |
||||||
|
], |
||||||
|
"link" => [ |
||||||
|
"@type" => "xsd:anyURI", |
||||||
|
], |
||||||
|
"list_float" => [ |
||||||
|
"@type" => "xsd:float", |
||||||
|
"@container" => "@list", |
||||||
|
], |
||||||
|
"list_integer" => [ |
||||||
|
"@type" => "xsd:int", |
||||||
|
"@container" => "@list", |
||||||
|
], |
||||||
|
"list_string" => [ |
||||||
|
"@type" => "xsd:string", |
||||||
|
"@container" => "@list", |
||||||
|
], |
||||||
|
"path" => [ |
||||||
|
"@type" => "xsd:anyURI", |
||||||
|
], |
||||||
|
"text" => [ |
||||||
|
"@type" => "xsd:string", |
||||||
|
], |
||||||
|
"text_with_summary" => [ |
||||||
|
"@type" => "xsd:string", |
||||||
|
], |
||||||
|
"text_long" => [ |
||||||
|
"@type" => "xsd:string", |
||||||
|
], |
||||||
|
"uuid" => [ |
||||||
|
"@type" => "xsd:string", |
||||||
|
], |
||||||
|
"uri" => [ |
||||||
|
"@type" => "xsd:anyURI", |
||||||
|
], |
||||||
|
"language" => [ |
||||||
|
"@type" => "xsd:language", |
||||||
|
], |
||||||
|
"string_long" => [ |
||||||
|
"@type" => "xsd:string", |
||||||
|
], |
||||||
|
"changed" => [ |
||||||
|
"@type" => "xsd:dateTime", |
||||||
|
], |
||||||
|
"map" => "xsd:", |
||||||
|
"boolean" => [ |
||||||
|
"@type" => "xsd:boolean", |
||||||
|
], |
||||||
|
"email" => [ |
||||||
|
"@type" => "xsd:string", |
||||||
|
], |
||||||
|
"integer" => [ |
||||||
|
"@type" => "xsd:int", |
||||||
|
], |
||||||
|
"decimal" => [ |
||||||
|
"@type" => "xsd:decimal", |
||||||
|
], |
||||||
|
"created" => [ |
||||||
|
"@type" => "xsd:dateTime", |
||||||
|
], |
||||||
|
"float" => [ |
||||||
|
"@type" => "xsd:float", |
||||||
|
], |
||||||
|
"entity_reference" => [ |
||||||
|
"@type" => "@id", |
||||||
|
], |
||||||
|
"timestamp" => [ |
||||||
|
"@type" => "xsd:dateTime", |
||||||
|
], |
||||||
|
"string" => [ |
||||||
|
"@type" => "xsd:string", |
||||||
|
], |
||||||
|
"password" => [ |
||||||
|
"@type" => "xsd:string", |
||||||
|
], |
||||||
|
]; |
||||||
|
|
||||||
|
return array_key_exists($field_type, $field_mappings) ? $field_mappings[$field_type] : $default_mapping; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,44 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\islandora\JsonldContextGenerator; |
||||||
|
|
||||||
|
use Drupal\rdf\RdfMappingInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Interface for a service that provides per Bundle JSON-LD Context generation. |
||||||
|
* |
||||||
|
* @ingroup: islandora |
||||||
|
*/ |
||||||
|
interface JsonldContextGeneratorInterface { |
||||||
|
|
||||||
|
/** |
||||||
|
* Generates an JSON-LD Context string based on an RdfMapping object. |
||||||
|
* |
||||||
|
* @param \Drupal\rdf\Entity\RdfMapping|RdfMappingInterface $mapping |
||||||
|
* An RDF Mapping Object. |
||||||
|
* |
||||||
|
* @return string |
||||||
|
* A JSON-LD @context as string. |
||||||
|
* |
||||||
|
* @throws \Exception |
||||||
|
* If no RDF mapping has no rdf:type assigned. |
||||||
|
*/ |
||||||
|
public function generateContext(RdfMappingInterface $mapping); |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns an JSON-LD Context string. |
||||||
|
* |
||||||
|
* This method should be invoked if caching and speed is required. |
||||||
|
* |
||||||
|
* @param string $ids |
||||||
|
* In the form of "entity_type.bundle_name". |
||||||
|
* |
||||||
|
* @return string |
||||||
|
* A JSON-LD @context as string. |
||||||
|
* |
||||||
|
* @throws \Exception |
||||||
|
* If no RDF mapping exists. |
||||||
|
*/ |
||||||
|
public function getContext($ids); |
||||||
|
|
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\islandora\Tests\Web; |
||||||
|
|
||||||
|
use Drupal\simpletest\WebTestBase; |
||||||
|
|
||||||
|
/** |
||||||
|
* Abstract base class for Islandora Web tests. |
||||||
|
*/ |
||||||
|
abstract class IslandoraWebTestBase extends WebTestBase { |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public static $modules = [ |
||||||
|
'system', |
||||||
|
'user', |
||||||
|
'field', |
||||||
|
'filter', |
||||||
|
'block', |
||||||
|
'node', |
||||||
|
'path', |
||||||
|
'text', |
||||||
|
'options', |
||||||
|
'inline_entity_form', |
||||||
|
'serialization', |
||||||
|
'rest', |
||||||
|
'rdf', |
||||||
|
'typed_data', |
||||||
|
'rules', |
||||||
|
'jsonld', |
||||||
|
'views', |
||||||
|
'key', |
||||||
|
'jwt', |
||||||
|
'islandora', |
||||||
|
]; |
||||||
|
|
||||||
|
} |
@ -0,0 +1,72 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\islandora\Tests\Web; |
||||||
|
|
||||||
|
use Drupal\Core\Url; |
||||||
|
|
||||||
|
/** |
||||||
|
* Implements WEB tests for Context routing response in various scenarios. |
||||||
|
* |
||||||
|
* @group islandora |
||||||
|
*/ |
||||||
|
class JsonldContextGeneratorWebTest extends IslandoraWebTestBase { |
||||||
|
|
||||||
|
/** |
||||||
|
* A user entity. |
||||||
|
* |
||||||
|
* @var \Drupal\Core\Session\AccountInterface |
||||||
|
*/ |
||||||
|
private $user; |
||||||
|
|
||||||
|
/** |
||||||
|
* Jwt does not define a config schema breaking this tests. |
||||||
|
* |
||||||
|
* @var bool |
||||||
|
*/ |
||||||
|
protected $strictConfigSchema = FALSE; |
||||||
|
|
||||||
|
/** |
||||||
|
* Initial setup tasks that for every method method. |
||||||
|
*/ |
||||||
|
public function setUp() { |
||||||
|
parent::setUp(); |
||||||
|
$this->user = $this->drupalCreateUser([ |
||||||
|
'administer site configuration', |
||||||
|
'view published fedora resource entities', |
||||||
|
'access content', |
||||||
|
] |
||||||
|
); |
||||||
|
// Login. |
||||||
|
$this->drupalLogin($this->user); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests that the Context Response Page can be reached. |
||||||
|
*/ |
||||||
|
public function testJsonldcontextPageExists() { |
||||||
|
$url = Url::fromRoute('entity.fedora_resource_type.jsonldcontext', ['bundle' => 'rdf_source']); |
||||||
|
$this->drupalGet($url); |
||||||
|
$this->assertResponse(200); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests that the response is in fact application/ld+json. |
||||||
|
*/ |
||||||
|
public function testJsonldcontextContentypeheaderResponseIsValid() { |
||||||
|
$url = Url::fromRoute('entity.fedora_resource_type.jsonldcontext', ['bundle' => 'rdf_source']); |
||||||
|
$this->drupalGet($url); |
||||||
|
$this->assertEqual($this->drupalGetHeader('Content-Type'), 'application/ld+json', 'Correct JSON-LD mime type was returned'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests that the Context received has the basic structural needs. |
||||||
|
*/ |
||||||
|
public function testJsonldcontextResponseIsValid() { |
||||||
|
$url = Url::fromRoute('entity.fedora_resource_type.jsonldcontext', ['bundle' => 'rdf_source']); |
||||||
|
$this->drupalGet($url); |
||||||
|
$jsonldarray = json_decode($this->getRawContent(), TRUE); |
||||||
|
// Check if the only key is "@context". |
||||||
|
$this->assertTrue(count(array_keys($jsonldarray)) == 1 && (key($jsonldarray) == '@context'), "JSON-LD to array encoded response has just one key and that key is @context"); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,43 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\Tests\islandora\Kernel; |
||||||
|
|
||||||
|
use Drupal\Component\Utility\Random; |
||||||
|
use Drupal\islandora\Entity\FedoraResourceType; |
||||||
|
|
||||||
|
/** |
||||||
|
* Trait that aids in the creation of a fedora resource type bundle. |
||||||
|
*/ |
||||||
|
trait FedoraContentTypeCreationTrait { |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a custom content Fedora Resource type based on default settings. |
||||||
|
* |
||||||
|
* @param array $values |
||||||
|
* An array of settings to change from the defaults. |
||||||
|
* Example: 'id' => 'some_bundle'. |
||||||
|
* |
||||||
|
* @return \Drupal\islandora\Entity\FedoraResourceType |
||||||
|
* Created content type. |
||||||
|
*/ |
||||||
|
protected function createFedoraResourceContentType(array $values = []) { |
||||||
|
// Find a non-existent random type name. |
||||||
|
$random = new Random(); |
||||||
|
if (!isset($values['type'])) { |
||||||
|
do { |
||||||
|
$id = strtolower($random->string(8)); |
||||||
|
} while (FedoraResourceType::load($id)); |
||||||
|
} |
||||||
|
else { |
||||||
|
$id = $values['type']; |
||||||
|
} |
||||||
|
$values += [ |
||||||
|
'id' => $id, |
||||||
|
'label' => $id, |
||||||
|
]; |
||||||
|
$type = FedoraResourceType::create($values); |
||||||
|
$type->save(); |
||||||
|
return $type; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,132 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\Tests\islandora\Kernel; |
||||||
|
|
||||||
|
use Drupal\islandora\JsonldContextGenerator\JsonldContextGenerator; |
||||||
|
use Drupal\KernelTests\KernelTestBase; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests the Json-LD context Generator methods and simple integration. |
||||||
|
* |
||||||
|
* @group islandora |
||||||
|
* @coversDefaultClass \Drupal\islandora\JsonldContextGenerator\JsonldContextGenerator |
||||||
|
*/ |
||||||
|
class JsonldContextGeneratorTest extends KernelTestBase { |
||||||
|
|
||||||
|
use FedoraContentTypeCreationTrait { |
||||||
|
createFedoraResourceContentType as drupalCreateFedoraContentType; |
||||||
|
} |
||||||
|
public static $modules = [ |
||||||
|
'system', |
||||||
|
'rdf', |
||||||
|
'islandora', |
||||||
|
'entity_test', |
||||||
|
'rdf_test_namespaces', |
||||||
|
]; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* The entity manager service. |
||||||
|
* |
||||||
|
* @var \Drupal\Core\Entity\EntityBundleListenerInterface |
||||||
|
*/ |
||||||
|
protected $entityBundleListener; |
||||||
|
|
||||||
|
/** |
||||||
|
* The state service. |
||||||
|
* |
||||||
|
* @var \Drupal\Core\State\StateInterface |
||||||
|
*/ |
||||||
|
protected $state; |
||||||
|
|
||||||
|
/** |
||||||
|
* The JsonldContextGenerator we are testing. |
||||||
|
* |
||||||
|
* @var \Drupal\islandora\JsonldContextGenerator\JsonldContextGeneratorInterface |
||||||
|
*/ |
||||||
|
protected $theJsonldContextGenerator; |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function setUp() { |
||||||
|
parent::setUp(); |
||||||
|
|
||||||
|
$types = ['schema:Thing']; |
||||||
|
$mapping = [ |
||||||
|
'properties' => ['schema:dateCreated'], |
||||||
|
'datatype' => 'xsd:dateTime', |
||||||
|
'datatype_callback' => ['callable' => 'Drupal\rdf\CommonDataConverter::dateIso8601Value'], |
||||||
|
]; |
||||||
|
|
||||||
|
// Save bundle mapping config. |
||||||
|
$rdfMapping = rdf_get_mapping('entity_test', 'rdf_source') |
||||||
|
->setBundleMapping(['types' => $types]) |
||||||
|
->setFieldMapping('created', $mapping) |
||||||
|
->save(); |
||||||
|
// Initialize our generator. |
||||||
|
$this->theJsonldContextGenerator = new JsonldContextGenerator( |
||||||
|
$this->container->get('entity_field.manager'), |
||||||
|
$this->container->get('entity_type.bundle.info'), |
||||||
|
$this->container->get('entity_type.manager'), |
||||||
|
$this->container->get('cache.default'), |
||||||
|
$this->container->get('logger.channel.islandora') |
||||||
|
); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @covers \Drupal\islandora\JsonldContextGenerator\JsonldContextGenerator::getContext |
||||||
|
*/ |
||||||
|
public function testGetContext() { |
||||||
|
// Test with known asserts. |
||||||
|
$context = $this->theJsonldContextGenerator->getContext('entity_test.rdf_source'); |
||||||
|
$context_as_array = json_decode($context, TRUE); |
||||||
|
$this->assertTrue(is_array($context_as_array), 'JSON-LD Context generated has correct structure for known Bundle'); |
||||||
|
|
||||||
|
$this->assertTrue(strpos($context, '"schema": "http://schema.org/"') !== FALSE, "JSON-LD Context generated contains the expected values for known Bundle"); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests Exception in case of no rdf type. |
||||||
|
* |
||||||
|
* @expectedException \Exception |
||||||
|
* @covers \Drupal\islandora\JsonldContextGenerator\JsonldContextGenerator::getContext |
||||||
|
*/ |
||||||
|
public function testGetContextException() { |
||||||
|
// This should throw the expected Exception. |
||||||
|
$newFedoraEntity = $this->drupalCreateFedoraContentType(); |
||||||
|
$this->theJsonldContextGenerator->getContext('fedora_resource.' . $newFedoraEntity->id()); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @covers \Drupal\islandora\JsonldContextGenerator\JsonldContextGenerator::generateContext |
||||||
|
*/ |
||||||
|
public function testGenerateContext() { |
||||||
|
// Test with known asserts. |
||||||
|
$rdfMapping = rdf_get_mapping('entity_test', 'rdf_source'); |
||||||
|
$context = $this->theJsonldContextGenerator->generateContext($rdfMapping); |
||||||
|
$context_as_array = json_decode($context, TRUE); |
||||||
|
$this->assertTrue(is_array($context_as_array), 'JSON-LD Context generated has correct structure for known Bundle'); |
||||||
|
|
||||||
|
$this->assertTrue(strpos($context, '"schema": "http://schema.org/"') !== FALSE, "JSON-LD Context generated contains the expected values for known Bundle"); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests Exception in case of no rdf type. |
||||||
|
* |
||||||
|
* @expectedException \Exception |
||||||
|
* @covers \Drupal\islandora\JsonldContextGenerator\JsonldContextGenerator::generateContext |
||||||
|
*/ |
||||||
|
public function testGenerateContextException() { |
||||||
|
// This should throw the expected Exception. |
||||||
|
$newFedoraEntity = $this->drupalCreateFedoraContentType(); |
||||||
|
$rdfMapping = rdf_get_mapping('fedora_resource', $newFedoraEntity->id()); |
||||||
|
$this->theJsonldContextGenerator->getContext('fedora_resource.' . $newFedoraEntity->id()); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue