Browse Source

JSON-LD Context generator (#33)

* 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 change
pull/756/head
Diego Pino Navarro 8 years ago committed by dannylamb
parent
commit
6ced405933
  1. 10
      .scripts/travis_setup.sh
  2. 4
      .travis.yml
  3. 25
      islandora.routing.yml
  4. 6
      islandora.services.yml
  5. 88
      src/Controller/FedoraResourceJsonLdContextController.php
  6. 424
      src/JsonldContextGenerator/JsonldContextGenerator.php
  7. 44
      src/JsonldContextGenerator/JsonldContextGeneratorInterface.php
  8. 38
      src/Tests/Web/IslandoraWebTestBase.php
  9. 72
      src/Tests/Web/JsonldContextGeneratorWebTest.php
  10. 43
      tests/src/Kernel/FedoraContentTypeCreationTrait.php
  11. 132
      tests/src/Kernel/JsonldContextGeneratorTest.php

10
.scripts/travis_setup.sh

@ -1,7 +1,7 @@
#!/bin/bash
echo "Setup database for Drupal"
mysql -u root -e 'create database drupal;'
mysql -u root -e "GRANT ALL PRIVILEGES ON drupal.* To 'drupal'@'localhost' IDENTIFIED BY 'drupal';"
mysql -u root -e "GRANT ALL PRIVILEGES ON drupal.* To 'drupal'@'127.0.0.1' IDENTIFIED BY 'drupal';"
if [ $TRAVIS_PHP_VERSION = "5.6" ]; then
phpenv config-add $SCRIPT_DIR/php56.ini
@ -30,11 +30,11 @@ phpenv rehash
echo "Drush setup drupal site"
cd web
drush si --db-url=mysql://drupal:drupal@localhost/drupal --yes
drush runserver --php-cgi=$HOME/.phpenv/shims/php-cgi localhost:8081 &>/tmp/drush_webserver.log &
drush si --db-url=mysql://drupal:drupal@127.0.0.1/drupal --yes
drush runserver 127.0.0.1:8282 &
until curl -s 127.0.0.1:8282; do true; done > /dev/null
echo "Enable simpletest module"
drush en -y simpletest
drush --uri=127.0.0.1:8282 en -y simpletest
echo "Setup ActiveMQ"
cd /opt

4
.travis.yml

@ -21,13 +21,13 @@ install:
- git -C "$TRAVIS_BUILD_DIR" checkout -b travis-testing
- cd $DRUPAL_DIR; composer config repositories.local path "$TRAVIS_BUILD_DIR"
- composer require "islandora/islandora:dev-travis-testing as dev-8.x-1.x" --prefer-source
- cd web; drush en -y islandora
- cd web; drush --uri=127.0.0.1:8282 en -y islandora
script:
- $SCRIPT_DIR/line_endings.sh $TRAVIS_BUILD_DIR
- phpcs --standard=Drupal --ignore=*.md --extensions=php,module,inc,install,test,profile,theme,css,info $TRAVIS_BUILD_DIR
- phpcpd --names *.module,*.inc,*.test,*.php $TRAVIS_BUILD_DIR
- php core/scripts/run-tests.sh --verbose --php `which php` islandora
- php core/scripts/run-tests.sh --url http://127.0.0.1:8282 --verbose --php `which php` --module "islandora"
notifications:
irc:

25
islandora.routing.yml

@ -1,16 +1,3 @@
# Islandora Routing definition
entity.fedora_resource_type.rdftest:
path: '/fedora_resource/{fedora_resource}/rdf'
defaults:
_controller: '\Drupal\node\Controller\NodePreviewController::view'
_title_callback: '\Drupal\node\Controller\NodePreviewController::title'
requirements:
_node_preview_access: '{node_preview}'
options:
parameters:
node_preview:
type: 'node_preview'
# Menu list of Islandora configuration forms
system.admin_config_islandora:
path: '/admin/config/islandora'
@ -28,3 +15,15 @@ system.islandora_settings:
_title: 'Islandora Settings'
requirements:
_permission: 'administer site configuration'
# Islandora JSON-LD Routing definition
entity.fedora_resource_type.jsonldcontext:
path: '/fedora_resource_context/{bundle}'
defaults:
_controller: '\Drupal\islandora\Controller\FedoraResourceJsonLdContextController::content'
requirements:
_permission: 'access content'
options:
parameters:
bundle:
type: entity:{fedora_resource}:{fedora_resource_type}

6
islandora.services.yml

@ -21,3 +21,9 @@ services:
islandora.versioncounter:
class: Drupal\islandora\VersionCounter\VersionCounter
arguments: ['@database']
logger.channel.islandora:
parent: logger.channel_base
arguments: ['islandora']
islandora.jsonldcontextgenerator:
class: Drupal\islandora\JsonldContextGenerator\JsonldContextGenerator
arguments: ['@entity_field.manager','@entity_type.bundle.info','@entity_type.manager', '@cache.default', '@logger.channel.islandora']

88
src/Controller/FedoraResourceJsonLdContextController.php

@ -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;
}
}

424
src/JsonldContextGenerator/JsonldContextGenerator.php

@ -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;
}
}

44
src/JsonldContextGenerator/JsonldContextGeneratorInterface.php

@ -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);
}

38
src/Tests/Web/IslandoraWebTestBase.php

@ -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',
];
}

72
src/Tests/Web/JsonldContextGeneratorWebTest.php

@ -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");
}
}

43
tests/src/Kernel/FedoraContentTypeCreationTrait.php

@ -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;
}
}

132
tests/src/Kernel/JsonldContextGeneratorTest.php

@ -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…
Cancel
Save