6 changed files with 48 additions and 581 deletions
@ -1,47 +0,0 @@
|
||||
<?php |
||||
|
||||
namespace Drupal\islandora; |
||||
|
||||
use Drupal\Core\Config\ConfigFactoryInterface; |
||||
use Drupal\islandora\Form\IslandoraSettingsForm; |
||||
use Islandora\Crayfish\Commons\Client\GeminiClient; |
||||
use Psr\Log\LoggerInterface; |
||||
use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException; |
||||
|
||||
/** |
||||
* Creates a GeminiClient as a Drupal service. |
||||
* |
||||
* @package Drupal\islandora |
||||
*/ |
||||
class GeminiClientFactory { |
||||
|
||||
/** |
||||
* Factory function. |
||||
* |
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config |
||||
* Config. |
||||
* @param \Psr\Log\LoggerInterface $logger |
||||
* The logger channel. |
||||
* |
||||
* @return \Islandora\Crayfish\Commons\Client\GeminiClient |
||||
* Return GeminiClient |
||||
* |
||||
* @throws \Exception |
||||
* If there is no URL to connect to. |
||||
*/ |
||||
public static function create(ConfigFactoryInterface $config, LoggerInterface $logger) { |
||||
// Get broker url from config. |
||||
$settings = $config->get(IslandoraSettingsForm::CONFIG_NAME); |
||||
$geminiUrl = $settings->get(IslandoraSettingsForm::GEMINI_URL); |
||||
|
||||
// Only attempt if there is one. |
||||
if (!empty($geminiUrl)) { |
||||
return GeminiClient::create($geminiUrl, $logger); |
||||
} |
||||
else { |
||||
$logger->notice("Attempted to create Gemini client without a Gemini URL defined."); |
||||
throw new PreconditionFailedHttpException("Unable to instantiate GeminiClient, missing Gemini URI in Islandora setting."); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -1,168 +0,0 @@
|
||||
<?php |
||||
|
||||
namespace Drupal\islandora; |
||||
|
||||
use Drupal\Core\Entity\EntityInterface; |
||||
use Drupal\islandora\MediaSource\MediaSourceService; |
||||
use Drupal\jwt\Authentication\Provider\JwtAuth; |
||||
use GuzzleHttp\Client; |
||||
use GuzzleHttp\Exception\RequestException; |
||||
use Islandora\Crayfish\Commons\Client\GeminiClient; |
||||
use Psr\Log\LoggerInterface; |
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; |
||||
|
||||
/** |
||||
* Locates the matching Fedora URI from the Gemini database. |
||||
* |
||||
* @package Drupal\islandora |
||||
*/ |
||||
class GeminiLookup { |
||||
|
||||
/** |
||||
* A GeminiClient. |
||||
* |
||||
* @var \Islandora\Crayfish\Commons\Client\GeminiClient |
||||
*/ |
||||
private $geminiClient; |
||||
|
||||
/** |
||||
* A JWT Provider service. |
||||
* |
||||
* @var \Drupal\jwt\Authentication\Provider\JwtAuth |
||||
*/ |
||||
private $jwtProvider; |
||||
|
||||
/** |
||||
* A MediaSourceService. |
||||
* |
||||
* @var \Drupal\islandora\MediaSource\MediaSourceService |
||||
*/ |
||||
private $mediaSource; |
||||
|
||||
/** |
||||
* An http client. |
||||
* |
||||
* @var \GuzzleHttp\Client |
||||
*/ |
||||
private $guzzle; |
||||
|
||||
/** |
||||
* The islandora logger channel. |
||||
* |
||||
* @var \Psr\Log\LoggerInterface |
||||
*/ |
||||
private $logger; |
||||
|
||||
/** |
||||
* GeminiField constructor. |
||||
* |
||||
* @param \Islandora\Crayfish\Commons\Client\GeminiClient $client |
||||
* The Gemini client. |
||||
* @param \Drupal\jwt\Authentication\Provider\JwtAuth $jwt_auth |
||||
* The JWT provider. |
||||
* @param \Drupal\islandora\MediaSource\MediaSourceService $media_source |
||||
* Media source service. |
||||
* @param \GuzzleHttp\Client $guzzle |
||||
* Guzzle client. |
||||
* @param \Psr\Log\LoggerInterface $logger |
||||
* The Islandora logger. |
||||
*/ |
||||
public function __construct( |
||||
GeminiClient $client, |
||||
JwtAuth $jwt_auth, |
||||
MediaSourceService $media_source, |
||||
Client $guzzle, |
||||
LoggerInterface $logger |
||||
) { |
||||
$this->geminiClient = $client; |
||||
$this->jwtProvider = $jwt_auth; |
||||
$this->mediaSource = $media_source; |
||||
$this->guzzle = $guzzle; |
||||
$this->logger = $logger; |
||||
} |
||||
|
||||
/** |
||||
* Lookup this entity's URI in the Gemini db and return the other URI. |
||||
* |
||||
* @param \Drupal\Core\Entity\EntityInterface $entity |
||||
* The entity to look for. |
||||
* |
||||
* @return string|null |
||||
* Return the URI or null |
||||
*/ |
||||
public function lookup(EntityInterface $entity) { |
||||
// Exit early if the entity hasn't been saved yet. |
||||
if ($entity->id() == NULL) { |
||||
return NULL; |
||||
} |
||||
|
||||
$is_media = $entity->getEntityTypeId() == 'media'; |
||||
|
||||
// Use the entity's uuid unless it's a media, |
||||
// use its file's uuid instead. |
||||
if ($is_media) { |
||||
try { |
||||
$file = $this->mediaSource->getSourceFile($entity); |
||||
$uuid = $file->uuid(); |
||||
} |
||||
// If the media has no source file, exit early. |
||||
catch (NotFoundHttpException $e) { |
||||
return NULL; |
||||
} |
||||
} |
||||
else { |
||||
$uuid = $entity->uuid(); |
||||
} |
||||
|
||||
// Look it up in Gemini. |
||||
$token = "Bearer " . $this->jwtProvider->generateToken(); |
||||
$urls = $this->geminiClient->getUrls($uuid, $token); |
||||
|
||||
// Exit early if there's no results from Gemini. |
||||
if (empty($urls)) { |
||||
return NULL; |
||||
} |
||||
|
||||
// If it's not a media, just return the url from Gemini;. |
||||
if (!$is_media) { |
||||
return $urls['fedora']; |
||||
} |
||||
|
||||
// If it's a media, perform a HEAD request against |
||||
// the file in Fedora and get its 'describedy' link header. |
||||
try { |
||||
$head = $this->guzzle->head( |
||||
$urls['fedora'], |
||||
['allow_redirects' => FALSE, 'headers' => ['Authorization' => $token]] |
||||
); |
||||
// phpcs:disable |
||||
if (class_exists(\GuzzleHttp\Psr7\Header::class)) { |
||||
$links = \GuzzleHttp\Psr7\Header::parse($head->getHeader('Link')); |
||||
} |
||||
else { |
||||
$links = \GuzzleHttp\Psr7\parse_header($head->getHeader('Link')); |
||||
} |
||||
//phpcs:enable |
||||
foreach ($links as $link) { |
||||
if ($link['rel'] == 'describedby') { |
||||
return trim($link[0], '<>'); |
||||
} |
||||
} |
||||
} |
||||
catch (RequestException $e) { |
||||
$this->logger->warning( |
||||
"Error performing Gemini lookup for media. Fedora HEAD to @url returned @status => @message", |
||||
[ |
||||
'@url' => $urls['fedora'], |
||||
'@status' => $e->getCode(), |
||||
'@message' => $e->getMessage, |
||||
] |
||||
); |
||||
return NULL; |
||||
} |
||||
|
||||
// Return null if no link header is found. |
||||
return NULL; |
||||
} |
||||
|
||||
} |
||||
@ -1,293 +0,0 @@
|
||||
<?php |
||||
|
||||
namespace Drupal\Tests\islandora\Kernel; |
||||
|
||||
use Drupal\Core\Entity\EntityInterface; |
||||
use Drupal\file\FileInterface; |
||||
use Drupal\islandora\GeminiLookup; |
||||
use Drupal\islandora\MediaSource\MediaSourceService; |
||||
use Drupal\jwt\Authentication\Provider\JwtAuth; |
||||
use Drupal\media\MediaInterface; |
||||
use GuzzleHttp\Client; |
||||
use GuzzleHttp\Psr7\Response; |
||||
use Islandora\Crayfish\Commons\Client\GeminiClient; |
||||
use Prophecy\Argument; |
||||
use Psr\Log\LoggerInterface; |
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; |
||||
|
||||
/** |
||||
* Tests Gemini Lookup. |
||||
* |
||||
* @group islandora |
||||
* @coversDefaultClass \Drupal\islandora\GeminiLookup |
||||
*/ |
||||
class GeminiLookupTest extends IslandoraKernelTestBase { |
||||
|
||||
/** |
||||
* JWT Auth. |
||||
* |
||||
* @var \Drupal\jwt\Authentication\Provider\JwtAuth |
||||
*/ |
||||
private $jwtAuth; |
||||
|
||||
/** |
||||
* Logger. |
||||
* |
||||
* @var \Psr\Log\LoggerInterface |
||||
*/ |
||||
private $logger; |
||||
|
||||
/** |
||||
* Guzzle. |
||||
* |
||||
* @var \GuzzleHttp\Client |
||||
*/ |
||||
private $guzzle; |
||||
|
||||
/** |
||||
* Gemini client. |
||||
* |
||||
* @var \Islandora\Crayfish\Commons\Client\GeminiClient |
||||
*/ |
||||
private $geminiClient; |
||||
|
||||
/** |
||||
* Media source service. |
||||
* |
||||
* @var \Drupal\islandora\MediaSource\MediaSourceService |
||||
*/ |
||||
private $mediaSource; |
||||
|
||||
/** |
||||
* An entity. |
||||
* |
||||
* @var \Drupal\Core\Entity\EntityInterface |
||||
*/ |
||||
private $entity; |
||||
|
||||
/** |
||||
* A media. |
||||
* |
||||
* @var \Drupal\media\MediaInterface |
||||
*/ |
||||
private $media; |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function setUp() { |
||||
parent::setUp(); |
||||
|
||||
// Mock up dummy objects by default. |
||||
$prophecy = $this->prophesize(JwtAuth::class); |
||||
$this->jwtAuth = $prophecy->reveal(); |
||||
|
||||
$prophecy = $this->prophesize(LoggerInterface::class); |
||||
$this->logger = $prophecy->reveal(); |
||||
|
||||
$prophecy = $this->prophesize(MediaSourceService::class); |
||||
$this->mediaSource = $prophecy->reveal(); |
||||
|
||||
$prophecy = $this->prophesize(GeminiClient::class); |
||||
$this->geminiClient = $prophecy->reveal(); |
||||
|
||||
$prophecy = $this->prophesize(Client::class); |
||||
$this->guzzle = $prophecy->reveal(); |
||||
|
||||
// Mock up an entity to use (node in this case). |
||||
$prophecy = $this->prophesize(EntityInterface::class); |
||||
$prophecy->id()->willReturn(1); |
||||
$prophecy->getEntityTypeId()->willReturn('node'); |
||||
$prophecy->uuid()->willReturn('abc123'); |
||||
$this->entity = $prophecy->reveal(); |
||||
|
||||
// Mock up a media to use. |
||||
$prophecy = $this->prophesize(MediaInterface::class); |
||||
$prophecy->id()->willReturn(1); |
||||
$prophecy->getEntityTypeId()->willReturn('media'); |
||||
$prophecy->uuid()->willReturn('abc123'); |
||||
$this->media = $prophecy->reveal(); |
||||
} |
||||
|
||||
/** |
||||
* Mocks up a gemini client that fails its lookup. |
||||
*/ |
||||
private function mockGeminiClientForFail() { |
||||
$prophecy = $this->prophesize(GeminiClient::class); |
||||
$prophecy->getUrls(Argument::any(), Argument::any()) |
||||
->willReturn([]); |
||||
$this->geminiClient = $prophecy->reveal(); |
||||
} |
||||
|
||||
/** |
||||
* Mocks up a gemini client that finds a fedora url. |
||||
*/ |
||||
private function mockGeminiClientForSuccess() { |
||||
$prophecy = $this->prophesize(GeminiClient::class); |
||||
$prophecy->getUrls(Argument::any(), Argument::any()) |
||||
->willReturn([ |
||||
'drupal' => '', |
||||
'fedora' => 'http://localhost:8080/fcrepo/rest/abc123', |
||||
]); |
||||
$this->geminiClient = $prophecy->reveal(); |
||||
} |
||||
|
||||
/** |
||||
* Mocks up a media source service that finds the source file for a media. |
||||
*/ |
||||
private function mockMediaSourceForSuccess() { |
||||
$prophecy = $this->prophesize(FileInterface::class); |
||||
$prophecy->uuid()->willReturn('abc123'); |
||||
$file = $prophecy->reveal(); |
||||
|
||||
$prophecy = $this->prophesize(MediaSourceService::class); |
||||
$prophecy->getSourceFile(Argument::any()) |
||||
->willReturn($file); |
||||
$this->mediaSource = $prophecy->reveal(); |
||||
} |
||||
|
||||
/** |
||||
* Make the gemini lookup out of class variables. |
||||
*/ |
||||
private function createGeminiLookup() { |
||||
return new GeminiLookup( |
||||
$this->geminiClient, |
||||
$this->jwtAuth, |
||||
$this->mediaSource, |
||||
$this->guzzle, |
||||
$this->logger |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @covers ::lookup |
||||
* @covers ::__construct |
||||
*/ |
||||
public function testEntityNotSaved() { |
||||
// Mock an entity that returns a null id. |
||||
// That means it's not saved in the db yet. |
||||
$prophecy = $this->prophesize(EntityInterface::class); |
||||
$prophecy->id()->willReturn(NULL); |
||||
$this->entity = $prophecy->reveal(); |
||||
|
||||
$gemini_lookup = $this->createGeminiLookup(); |
||||
|
||||
$this->assertEquals( |
||||
NULL, |
||||
$gemini_lookup->lookup($this->entity) |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @covers ::lookup |
||||
* @covers ::__construct |
||||
*/ |
||||
public function testEntityNotFound() { |
||||
$this->mockGeminiClientForFail(); |
||||
|
||||
$gemini_lookup = $this->createGeminiLookup(); |
||||
|
||||
$this->assertEquals( |
||||
NULL, |
||||
$gemini_lookup->lookup($this->entity) |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @covers ::lookup |
||||
* @covers ::__construct |
||||
*/ |
||||
public function testEntityFound() { |
||||
$this->mockGeminiClientForSuccess(); |
||||
|
||||
$gemini_lookup = $this->createGeminiLookup(); |
||||
|
||||
$this->assertEquals( |
||||
'http://localhost:8080/fcrepo/rest/abc123', |
||||
$gemini_lookup->lookup($this->entity) |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @covers ::lookup |
||||
* @covers ::__construct |
||||
*/ |
||||
public function testMediaHasNoSourceFile() { |
||||
// Mock a media source service that fails to find |
||||
// the source file for a media. |
||||
$prophecy = $this->prophesize(MediaSourceService::class); |
||||
$prophecy->getSourceFile(Argument::any()) |
||||
->willThrow(new NotFoundHttpException("Media has no source")); |
||||
$this->mediaSource = $prophecy->reveal(); |
||||
|
||||
$gemini_lookup = $this->createGeminiLookup(); |
||||
|
||||
$this->assertEquals( |
||||
NULL, |
||||
$gemini_lookup->lookup($this->media) |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @covers ::lookup |
||||
* @covers ::__construct |
||||
*/ |
||||
public function testMediaNotFound() { |
||||
$this->mockMediaSourceForSuccess(); |
||||
$this->mockGeminiClientForFail(); |
||||
|
||||
$gemini_lookup = $this->createGeminiLookup(); |
||||
|
||||
$this->assertEquals( |
||||
NULL, |
||||
$gemini_lookup->lookup($this->media) |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @covers ::lookup |
||||
* @covers ::__construct |
||||
*/ |
||||
public function testFileFoundButNoDescribedby() { |
||||
$this->mockMediaSourceForSuccess(); |
||||
$this->mockGeminiClientForSuccess(); |
||||
|
||||
// Mock up a guzzle client that does not return |
||||
// the describedby header. |
||||
$prophecy = $this->prophesize(Client::class); |
||||
$prophecy->head(Argument::any(), Argument::any()) |
||||
->willReturn(new Response(200, [])); |
||||
$this->guzzle = $prophecy->reveal(); |
||||
|
||||
$gemini_lookup = $this->createGeminiLookup(); |
||||
|
||||
$this->assertEquals( |
||||
NULL, |
||||
$gemini_lookup->lookup($this->media) |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @covers ::lookup |
||||
* @covers ::__construct |
||||
*/ |
||||
public function testMediaFound() { |
||||
$this->mockMediaSourceForSuccess(); |
||||
$this->mockGeminiClientForSuccess(); |
||||
|
||||
// Mock up a guzzle client that returns |
||||
// the describedby header. |
||||
$prophecy = $this->prophesize(Client::class); |
||||
$prophecy->head(Argument::any(), Argument::any()) |
||||
->willReturn(new Response(200, ['Link' => '<http://localhost:8080/fcrepo/rest/abc123/fcr:metadata>; rel="describedby"'])); |
||||
$this->guzzle = $prophecy->reveal(); |
||||
|
||||
$gemini_lookup = $this->createGeminiLookup(); |
||||
|
||||
$this->assertEquals( |
||||
'http://localhost:8080/fcrepo/rest/abc123/fcr:metadata', |
||||
$gemini_lookup->lookup($this->media) |
||||
); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue