Skip to content

Commit

Permalink
Merge pull request #9 from discoverygarden/feature/cacheability
Browse files Browse the repository at this point in the history
PER-42: Attempt to roll some basic cacheability.
  • Loading branch information
nchiasson-dgi authored Mar 7, 2024
2 parents ff33244 + a1aa5dd commit 89581ea
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 37 deletions.
54 changes: 42 additions & 12 deletions src/Controller/V3/ManifestController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@

namespace Drupal\iiif_presentation_api\Controller\V3;

use Drupal\Core\Cache\CacheableJsonResponse;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\serialization\Normalizer\CacheableNormalizerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Serializer\SerializerInterface;

/**
Expand All @@ -20,13 +24,21 @@ class ManifestController extends ControllerBase {
*/
protected SerializerInterface $serializer;

/**
* Renderer service.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected RendererInterface $renderer;

/**
* {@inheritDoc}
*/
public static function create(ContainerInterface $container) {
$instance = parent::create($container);

$instance->serializer = $container->get('serializer');
$instance->renderer = $container->get('renderer');

return $instance;
}
Expand All @@ -40,17 +52,35 @@ public static function create(ContainerInterface $container) {
* The route match object.
*/
public function build(string $parameter_name, RouteMatchInterface $route_match) {
$_entity = $route_match->getParameter($parameter_name);
return (new JsonResponse(
$this->serializer->serialize($_entity, 'iiif-p-v3'),
200,
[
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'GET',
],
TRUE
));
// XXX: Seems like something leaking cache metadata, explicitly wrap things
// up in a render context to capture and attach it.
$context = new RenderContext();
/** @var \Drupal\Core\Cache\CacheableJsonResponse $response */
$response = $this->renderer->executeInRenderContext($context, function () use ($parameter_name, $route_match) {
$_entity = $route_match->getParameter($parameter_name);
$cache_meta = new CacheableMetadata();
$context = [
CacheableNormalizerInterface::SERIALIZATION_CONTEXT_CACHEABILITY => $cache_meta,
];
$serialized = $this->serializer->serialize($_entity, 'iiif-p-v3', $context);
return (new CacheableJsonResponse(
$serialized,
200,
[
'Access-Control-Allow-Credentials' => 'true',
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'GET',
],
TRUE
))->addCacheableDependency($cache_meta);
});

if (!$context->isEmpty()) {
$metadata = $context->pop();
$response->addCacheableDependency($metadata);
}

return $response;
}

/**
Expand Down
6 changes: 5 additions & 1 deletion src/Event/V3/ContentEntityExtrasEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
namespace Drupal\iiif_presentation_api\Event\V3;

use Drupal\Component\EventDispatcher\Event;
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
use Drupal\Core\Entity\EntityInterface;

/**
* Content entity service gathering event.
*/
class ContentEntityExtrasEvent extends Event {
class ContentEntityExtrasEvent extends Event implements RefinableCacheableDependencyInterface {

use RefinableCacheableDependencyTrait;

/**
* Built out set of extra properties to set in the manifest.
Expand Down
6 changes: 5 additions & 1 deletion src/Event/V3/ImageBodyEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
namespace Drupal\iiif_presentation_api\Event\V3;

use Drupal\Component\EventDispatcher\Event;
use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
use Drupal\file\FileInterface;

/**
* Image body generation event.
*/
class ImageBodyEvent extends Event {
class ImageBodyEvent extends Event implements RefinableCacheableDependencyInterface {

use RefinableCacheableDependencyTrait;

/**
* The bodies added to the event.
Expand Down
48 changes: 28 additions & 20 deletions src/EventSubscriber/V3/BaseImageBodyEventSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Url;
use Drupal\file\FileInterface;
use Drupal\iiif_presentation_api\Event\V3\ImageBodyEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

Expand Down Expand Up @@ -46,6 +45,7 @@ public function baseBody(ImageBodyEvent $event) : void {
'format' => $file->getMimeType(),
'service' => [],
]);
$event->addCacheableDependency($file);
}

/**
Expand All @@ -54,35 +54,45 @@ public function baseBody(ImageBodyEvent $event) : void {
* @param string|null $slug
* A URL slug for the endpoint. If provided, should have an `{identifier}`
* portion that we will replace with an ID.
* @param \Drupal\file\FileInterface $file
* The file for which to generate a bod.
* @param \Drupal\iiif_presentation_api\Event\V3\ImageBodyEvent $event
* The event for which we are generating a body.
* @param array $extra
* An associative array of extra values to be set in the body.
* @param string $size
* The requested size.
* @param string|null $size
* The requested size, or NULL to use the size specified in the event.
*
* @return array
* The body.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
*/
protected function getBody(?string $slug, FileInterface $file, array $extra = [], string $size = 'full') : array {
protected function getBody(?string $slug, ImageBodyEvent $event, array $extra = [], ?string $size = NULL) : array {
if (!$slug) {
return [];
}

$file = $event->getImage();
$size ??= $event->getSize();

$id_plugin = $this->idPluginManager->createInstance(getenv('IIIF_IMAGE_ID_PLUGIN') ?: 'identity');
$base_id = strtr($slug, [
'{identifier}' => rawurlencode($id_plugin->getIdentifier($file)),
]);
$generated_body_id = Url::fromUri("{$base_id}/full/{$size}/0/default.jpg", ['absolute' => TRUE])->toString(TRUE);
$service_id = Url::fromUri($base_id, ['absolute' => TRUE])->toString(TRUE);

$event->addCacheableDependency($generated_body_id)
->addCacheableDependency($service_id)
->addCacheableDependency($file);

return [
'id' => Url::fromUri("{$base_id}/full/{$size}/0/default.jpg", ['absolute' => TRUE])->toString(),
'id' => $generated_body_id->getGeneratedUrl(),
'type' => 'Image',
'format' => 'image/jpeg',
'service' => [
[
// @todo Add in auth in some manner.
'id' => Url::fromUri($base_id, ['absolute' => TRUE])->toString(),
'id' => $service_id->getGeneratedUrl(),
] + $extra,
],
];
Expand All @@ -92,50 +102,48 @@ protected function getBody(?string $slug, FileInterface $file, array $extra = []
* Event callback; build body for IIIF Image API v1.
*/
public function imageV1Body(ImageBodyEvent $event) : void {
// @todo Validate that the size spec is valid for IIIF-I V1, maybe map to
// something similar if unsupported?
$event->addBody($this->getBody(
getenv('IIIF_IMAGE_V1_SLUG'),
$event->getImage(),
$event,
[
'type' => 'ImageService1',
'profile' => 'level2',
],
// @todo Validate that the size spec is valid for IIIF-I V1, maybe map to
// something similar if unsupported?
$event->getSize(),
));
}

/**
* Event callback; build body for IIIF Image API v2.
*/
public function imageV2Body(ImageBodyEvent $event) : void {
// @todo Validate that the size spec is valid for IIIF-I V2, maybe map to
// something similar if unsupported?
$event->addBody($this->getBody(
getenv('IIIF_IMAGE_V2_SLUG'),
$event->getImage(),
$event,
[
'type' => 'ImageService2',
'profile' => 'level2',
],
// @todo Validate that the size spec is valid for IIIF-I V2, maybe map to
// something similar if unsupported?
$event->getSize(),

));
}

/**
* Event callback; build body for IIIF Image API v3.
*/
public function imageV3Body(ImageBodyEvent $event) : void {
// @todo Validate that the size spec is valid for IIIF-I V3, maybe map to
// something similar if unsupported?
$event->addBody($this->getBody(
getenv('IIIF_IMAGE_V3_SLUG'),
$event->getImage(),
$event,
[
'type' => 'ImageService3',
'profile' => 'level2',
],
// @todo Validate that the size spec is valid for IIIF-I V3, maybe map to
// something similar if unsupported?
$event->getSize(),
));
}

Expand Down
5 changes: 2 additions & 3 deletions src/Normalizer/V3/ContentEntityNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,7 @@ public function normalize($object, $format = NULL, array $context = []) {
'account' => $this->user,
];

if (isset($context[static::SERIALIZATION_CONTEXT_CACHEABILITY])) {
$context[static::SERIALIZATION_CONTEXT_CACHEABILITY]->addCacheContexts(['user.roles']);
}
$context[static::SERIALIZATION_CONTEXT_CACHEABILITY]?->addCacheContexts(['user.roles']);

$context['parent'] = [
'type' => $normalized['type'],
Expand All @@ -102,6 +100,7 @@ public function normalize($object, $format = NULL, array $context = []) {
$normalized,
$context,
));
$this->addCacheableDependency($context, $service_event);
if ($extras = $service_event->getExtras()) {
$normalized = NestedArray::mergeDeep($normalized, $extras);
}
Expand Down

0 comments on commit 89581ea

Please sign in to comment.