- DOI implementation wit unit testing

This commit is contained in:
Arno Kaimbacher 2021-02-26 17:02:07 +01:00
parent 7f9bd089b1
commit 9b6a6469d7
20 changed files with 2128 additions and 360 deletions

View file

@ -0,0 +1,203 @@
<?php
namespace App\Http\Controllers;
use App\Interfaces\DOIInterface;
use App\Models\Dataset;
use App\Models\DatasetIdentifier;
use Illuminate\Http\Request;
use App\Models\Oai\OaiModelError;
use App\Exceptions\OaiModelException;
use Illuminate\Support\Facades\Log;
class DoiController extends Controller
{
protected $doiService;
protected $LaudatioUtils;
/**
* Holds xml representation of document information to be processed.
*
* @var \DomDocument Defaults to null.
*/
protected $xml = null;
/**
* Holds the stylesheet for the transformation.
*
* @var \DomDocument Defaults to null.
*/
protected $xslt = null;
/**
* Holds the xslt processor.
*
* @var \XSLTProcessor Defaults to null.
*/
protected $proc = null;
/**
* DOIController constructor.
* @param DoiInterface $DOIService
*/
public function __construct(DoiInterface $DoiClient)
{
$this->doiClient = $DoiClient;
$this->xml = new \DomDocument();
$this->proc = new \XSLTProcessor();
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create(Request $request)
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$dataId = $request->input('publish_id');
// Setup stylesheet
$this->loadStyleSheet(public_path() .'\prefixes\doi_datacite.xslt');
// set timestamp
$date = new \DateTime();
$unixTimestamp = $date->getTimestamp();
$this->proc->setParameter('', 'unixTimestamp', $unixTimestamp);
$prefix = config('tethys.datacite_prefix');
$this->proc->setParameter('', 'prefix', $prefix);
$repIdentifier = "tethys";
$this->proc->setParameter('', 'repIdentifier', $repIdentifier);
$this->xml->appendChild($this->xml->createElement('Datasets'));
$dataset = Dataset::where('publish_id', '=', $dataId)->firstOrFail();
if (is_null($dataset)) {
throw new OaiModelException('Dataset is not available for registering DOI!', OaiModelError::NORECORDSMATCH);
}
$dataset->fetchValues();
$xmlModel = new \App\Library\Xml\XmlModel();
$xmlModel->setModel($dataset);
$xmlModel->excludeEmptyFields();
$cache = ($dataset->xmlCache) ? $dataset->xmlCache : new \App\Models\XmlCache();
$xmlModel->setXmlCache($cache);
$domNode = $xmlModel->getDomDocument()->getElementsByTagName('Rdr_Dataset')->item(0);
$node = $this->xml->importNode($domNode, true);
$this->addSpecInformation($node, 'data-type:' . $dataset->type);
$this->xml->documentElement->appendChild($node);
$xmlMeta = $this->proc->transformToXML($this->xml);
Log::alert($xmlMeta);
//create doiValue and correspunfing landingpage of tehtys
$doiValue = $prefix . '/tethys.' . $dataset->publish_id;
$appUrl = config('app.url');
$landingPageUrl = $appUrl . "/dataset/" . $dataset->publish_id;
$response = $this->doiClient->registerDoi($doiValue, $xmlMeta, $landingPageUrl);
if ($response->getStatusCode() == 201) {
$doi = new DatasetIdentifier();
$doi['value'] = $doiValue;
$doi['dataset_id'] = $dataset->id;
$doi['type'] = "doi";
$doi['status'] = "registered";
$doi['registration_ts'] = now();
// $doi->save();
}
}
/**
* Display the specified resource.
*
* @param \App\Models\DatasetIdentifier $doi
* @return \Illuminate\Http\Response
*/
public function show(DatasetIdentifier $doi)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\DatasetIdentifier $doi
* @return \Illuminate\Http\Response
*/
public function edit(DatasetIdentifier $doi)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\DatasetIdentifier $doi
* @return \Illuminate\Http\Response
*/
public function update(Request $request, DatasetIdentifier $doi)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\DatasetIdentifier $doi
* @return \Illuminate\Http\Response
*/
public function destroy(DatasetIdentifier $doi)
{
//
}
/**
* Load an xslt stylesheet.
*
* @return void
*/
private function loadStyleSheet($stylesheet)
{
$this->xslt = new \DomDocument;
$this->xslt->load($stylesheet);
$this->proc->importStyleSheet($this->xslt);
if (isset($_SERVER['HTTP_HOST'])) {
$this->proc->setParameter('', 'host', $_SERVER['HTTP_HOST']);
}
//$this->proc->setParameter('', 'server', $this->getRequest()->getBaseUrl());
}
private function addSpecInformation(\DOMNode $document, $information)
{
$setSpecAttribute = $this->xml->createAttribute('Value');
$setSpecAttributeValue = $this->xml->createTextNode($information);
$setSpecAttribute->appendChild($setSpecAttributeValue);
$setSpecElement = $this->xml->createElement('SetSpec');
//$setSpecElement =new \DOMElement("SetSpec");
$setSpecElement->appendChild($setSpecAttribute);
$document->appendChild($setSpecElement);
}
}

View file

@ -0,0 +1,16 @@
<?php
/**
* Created by Visual Studio Code.
* User: kaiarn
* Date: 19.02.2021
*/
namespace App\Interfaces;
interface DoiInterface
{
public function registerDoi($doiValue, $xmlMeta, $landingPageUrl);
public function getMetadataForDoi($identifier);
public function updateMetadataForDoi($identifier, $new_meta);
public function deleteMetadataForDoi($identifier);
// public function deleteDoiByCurlRequest($doi);
}

View file

@ -0,0 +1,19 @@
<?php
namespace App\Models;
use App\Models\Dataset;
use Illuminate\Database\Eloquent\Model;
class DatasetIdentifier extends Model
{
protected $table = 'dataset_identifierss';
protected $guarded = array();
/**
* The dataset that belong to the DocumentIdentifier.
*/
public function dataset()
{
return $this->belongsTo(Dataset::class, 'document_id', 'id');
}
}

View file

@ -0,0 +1,44 @@
<?php
/**
* Visual Studio Code.
* User: kaiarn
* Date: 19.02.21
*/
namespace App\Providers;
use App\Tethys\Utils\DoiService;
use Illuminate\Support\ServiceProvider;
class DoiServiceProvider extends ServiceProvider
{
protected $defer = true;
/**
* Bootstrap the application services.
*
* @return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* @return void
*/
public function register()
{
//
$this->app->singleton('App\Interfaces\DoiInterface', function ($app) {
return new DoiService();
});
}
public function provides()
{
return [DoiService::class];
}
}

View file

@ -0,0 +1,212 @@
<?php
namespace App\Tethys\Utils;
use GuzzleHttp\Client;
use Illuminate\Support\Facades\Log;
use App\Interfaces\DoiInterface;
class DoiClient implements DoiInterface
{
private $username;
private $password;
private $serviceUrl;
public function __construct()
{
$datacite_environment = "production"; //config('tethys.datacite_environment');
if ($datacite_environment == "debug") {
$this->username = config('tethys.datacite_test_username');
$this->password = config('tethys.datacite_test_password');
$this->serviceUrl = config('tethys.test_datacite_service_url');
$this->prefix = config('tethys.datacite_prefix');
} elseif ($datacite_environment == "production") {
$this->username = config('tethys.datacite_username');
$this->password = config('tethys.datacite_password');
$this->serviceUrl = config('tethys.datacite_service_url');
$this->prefix = config('tethys.datacite_prefix');
}
if (is_null($this->username) || is_null($this->password) || is_null($this->serviceUrl)) {
$message = 'missing configuration settings to properly initialize DOI client';
Log::error($message);
throw new DoiClientException($message);
}
}
/**
* Creates a DOI or ARK with the given identifier
*
* @param string $identifier The desired DOI identifier e.g. '10.5072/tethys.999',
* @param $xmlMeta
* @param $landingPageUrl e.g. https://www.tethys.at/dataset/1
*
* @return GuzzleHttp\Psr7\Response The http response in the form of a Guzzle response
*/
public function registerDoi($doiValue, $xmlMeta, $landingPageUrl)
{
// Schritt 1: Metadaten als XML registrieren
$response = null;
$url = $this->serviceUrl . '/metadata/' . $doiValue;
try {
$client = new Client([
'auth' => [$this->username, $this->password],
// 'base_uri' => $url,
'verify' => false,
'headers' => [
'Content-Type' => 'application/xml;charset=UTF-8',
],
// 'body' => $xmlMeta,
]);
// Provide the body as a string.
$response = $client->request('PUT', $url, [
'body' => $xmlMeta,
]);
} catch (\Exception $e) {
$message = 'request to ' . $url . ' failed with ' . $e->getMessage();
// $this->log($message, 'err');
throw new DoiClientException($message);
}
// Response Codes
// 201 Created: operation successful
// 400 Bad Request: invalid XML, wrong prefix
// 401 Unauthorised: no login
// 403 Forbidden: login problem, quota exceeded
// 415 Wrong Content Type : Not including content type in the header.
// 422 Unprocessable Entity : invalid XML
if ($response->getStatusCode() != 201) {
$message = 'unexpected DataCite MDS response code ' . $response->getStatusCode();
// $this->log($message, 'err');
throw new DoiClientException($message);
}
// Schritt 2: Register the DOI name
// DOI und URL der Frontdoor des zugehörigen Dokuments übergeben
$url = $this->serviceUrl . '/doi/' . $doiValue;
try {
$client = new Client(
[
'auth' => [$this->username, $this->password],
'verify' => false,
'headers' => [
'Content-Type' => 'text/plain;charset=UTF-8',
],
]
);
$data = "doi=$doiValue\nurl=" . $landingPageUrl;
// $client->setRawData($data, 'text/plain;charset=UTF-8');
$response = $client->request('PUT', $url, [
'body' => $data,
'headers' => [
'Content-Type' => 'text/plain;charset=UTF-8',
],
]);
} catch (\Exception $e) {
$message = 'request to ' . $url . ' failed with ' . $e->getMessage();
// $this->log($message, 'err');
throw new DoiClientException($message);
}
// Response Codes
// 201 Created: operation successful
// 400 Bad Request: request body must be exactly two lines: DOI and URL; wrong domain, wrong prefix;
// 401 Unauthorised: no login
// 403 Forbidden: login problem, quota exceeded
// 412 Precondition failed: metadata must be uploaded first.
// $this->log('DataCite response status code (expected 201): ' . $response->getStatus());
// $this->log('DataCite response body: ' . $response->getBody());
if ($response->getStatusCode() != 201) {
$message = 'unexpected DataCite MDS response code ' . $response->getStatusCode();
// $this->log($message, 'err');
throw new DoiClientException($message);
}
return $response;
}
/* Response Status Codes
* 200 OK: operation successful
* 204 No Content : DOI is known to DataCite Metadata Store (MDS), but is not minted (or not resolvable e.g. due
* to handle's latency)
* 401 Unauthorized: no login
* 403 Login problem or dataset belongs to another party
* 404 Not Found: DOI does not exist in our database (e.g. registration pending)
*
* @param $doiValue
* @param $landingPageURL
*
* @return bool Methode liefert true, wenn die DOI erfolgreich registiert wurde und die Prüfung positiv ausfällt.
*
* @throws ClientException
*
*/
public function checkDoi($doiValue, $landingPageURL): bool
{
$response = null;
$url = $this->serviceUrl . '/doi/' . $doiValue;
try {
$client = new Client([
'base_uri' => $this->serviceUrl . '/doi/' . $doiValue,
'auth' => [$this->username, $this->password],
'verify' => false,
]);
$response = $client->request('GET');
} catch (\Exception $e) {
$message = 'request to ' . $url . ' failed with ' . $e->getMessage();
Log::error($message);
// throw new \Exception($message);
return false;
}
$statusCode = $response->getStatusCode();
// in $body steht die URL zur Frontdoor, die mit der DOI verknüpft wurde
$body = $response->getBody();
// $this->log('DataCite response status code (expected 200): ' . $statusCode);
// $this->log('DataCite response body (expected ' . $landingPageURL . '): ' . $body);
return ($statusCode == 200 && $landingPageURL == $body);
}
public function getMetadataForDoi($identifier)
{
//
}
public function updateMetadataForDoi($identifier, $new_meta)
{
//
}
/**
* Markiert den Datensatz zur übergebenen DOI als inaktiv.
*
* @param $doiValue
*
* @throws ClientException
*/
public function deleteMetadataForDoi($doiValue)
{
$response = null;
$url = $this->serviceUrl . '/metadata/' . $doiValue;
try {
$client = new Client([
'base_uri' => $url,
'auth' => [$this->username, $this->password],
'verify' => false,
]);
$response = $client->request('DELETE');
} catch (\Exception $e) {
$message = 'request to ' . $url . ' failed with ' . $e->getMessage();
Log::error($message, 'err');
throw new DoiClientException($message);
}
// $this->log('DataCite response status code (expected 200): ' . $response->getStatus());
if ($response->getStatusCode() != 200) {
$message = 'unexpected DataCite MDS response code ' . $response->getStatusCode();
Log::error($message, 'err');
throw new DoiClientException($message);
}
}
}

View file

@ -0,0 +1,7 @@
<?php
namespace App\Tethys\Utils;
class DoiClientException extends \Exception
{
}