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