Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alternative Islandora ResourceService using providers #135

Merged
merged 7 commits into from
Jan 24, 2016
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Alt Islandora ResourceService using providers
Changed the one-file logic to full ServiceProvider and
ControllerProvider. Allowed some external service injection/swapping
possibilities for users, had to move the global middleware to specific
middleware for each route to allow other silex apps to use and avoid
all the transforms we do here
DiegoPino committed Jan 20, 2016

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit 564c2e0bcebc71ad59f70d043d9e885ae719a926
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -6,3 +6,5 @@ install/downloads
site/

services/ResourceService/composer.lock

services/ResourceServiceProvider/composer.lock
1 change: 1 addition & 0 deletions services/ResourceServiceProvider/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
vendor/
26 changes: 26 additions & 0 deletions services/ResourceServiceProvider/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "islandora/resource-service",
"description": "RESTful service providing resources in Fedora 4",
"repositories": [
{
"type": "vcs",
"url": "/Users/dpino/Desktop/Development/ISLANDORAWORK/CLAW_MICRO/chullo"
}
],
"require": {
"islandora/chullo": "dev-api",
"silex/silex": "^1.3",
"symfony/config": "^3.0",
"twig/twig": "^1.23",
"symfony/yaml": "^3.0"
},
"autoload": {
"psr-4": {"Islandora\\ResourceService\\": "src/"}
},
"authors": [
{
"name": "Diego Pino Navarro",
"email": "dpino@krayon.cl"
}
]
}
9 changes: 9 additions & 0 deletions services/ResourceServiceProvider/config/settings.dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Islandora Dev Settings to be used with $app['debug'] == TRUE
islandora:
fedoraProtocol: http
fedoraHost: "localhost:8080"
fedoraPath: /rest
tripleProtocol: http
tripleHost: "localhost:9999"
triplePath: /bigdata/sparql
resourceIdRegex: "(?:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})?"
8 changes: 8 additions & 0 deletions services/ResourceServiceProvider/config/settings.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
islandora:
fedoraProtocol: http
fedoraHost: "localhost:8080"
fedoraPath: /fcrepo/rest
tripleProtocol: http
tripleHost: "localhost:8080"
triplePath: /bigdata/sparql
resourceIdRegex: "(?:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})?"
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

namespace Islandora\ResourceService\Controller;
use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class ResourceController {
/**
* Resource GET controller takes $id (valid UUID or empty) as first value to match, optional a child resource path
* takes 'rx' and/or 'metadata' as optional query arguments
* @see https://wiki.duraspace.org/display/FEDORA40/RESTful+HTTP+API#RESTfulHTTPAPI-GETRetrievethecontentoftheresource
*/
public function get(Application $app, Request $request, $id, $child) {
$tx = $request->query->get('tx', "");
$metadata = $request->query->get('metadata', FALSE) ? '/fcr:metadata' : "";
try {
$response = $app['api']->getResource($app->escape($id).'/'.$child.$metadata, $request->headers->all(), $tx);
}
catch (\Exception $e) {
$app->abort(503, 'Chullo says "Fedora4 Repository Not available"');
}
return $response;
}
/**
* Resource POST route controller. takes $id (valid UUID or empty) for the parent resource as first value to match
* takes 'rx' and/or 'checksum' as optional query arguments
* @see https://wiki.duraspace.org/display/FEDORA40/RESTful+HTTP+API#RESTfulHTTPAPI-BluePOSTCreatenewresourceswithinaLDPcontainer
*/
public function post(Application $app, Request $request, $id) {
$tx = $request->query->get('tx', "");
$checksum = $request->query->get('checksum', "");
try {
$response = $app['api']->createResource($app->escape($id), $request->getContent(), $request->headers->all(), $tx, $checksum);
}
catch (\Exception $e) {
$app->abort(503, '"Chullo says Fedora4 Repository is Not available"');
}
return $response;
}
/**
* Resource PUT route. takes $id (valid UUID or empty) for the resource to be update/created as first value to match,
* optional a Child resource relative path
* takes 'rx' and/or 'checksum' as optional query arguments
* @see https://wiki.duraspace.org/display/FEDORA40/RESTful+HTTP+API#RESTfulHTTPAPI-YellowPUTCreatearesourcewithaspecifiedpath,orreplacethetriplesassociatedwitharesourcewiththetriplesprovidedintherequestbody.
*/
public function put(Application $app, Request $request, $id, $child) {
$tx = $request->query->get('tx', "");
$checksum = $request->query->get('checksum', "");
try {
$response = $app['api']->saveResource($app->escape($id).'/'.$child, $request->getContent(), $request->headers->all(), $tx, $checksum);
}
catch (\Exception $e) {
$app->abort(503, '"Chullo says Fedora4 Repository is Not available"');
}
return $response;
}
public function patch(Application $app, Request $request, $id, $child) {
$tx = $request->query->get('tx', "");
try {
$response = $app['api']->modifyResource($app->escape($id).'/'.$child, $request->getContent(), $request->headers->all(), $tx);
}
catch (\Exception $e) {
$app->abort(503, '"Chullo says Fedora4 Repository is Not available"');
}
return $response;
}
public function delete(Application $app, Request $request, $id, $child) {
$tx = $request->query->get('tx', "");
$force = $request->query->get('force', FALSE);
try {
$response = $app['api']->deleteResource($app->escape($id).'/'.$child, $tx);
//remove tombstone also if 'force' == true and previous response is 204
if ((204 == $response->getStatusCode() || 410 == $response->getStatusCode()) && $force) {
$response= $app['api']->deleteResource($app->escape($id).'/'.$child.'/fcr:tombstone', $tx);
}
}
catch (\Exception $e) {
$app->abort(503, '"Chullo says Fedora4 Repository is Not available"');
}
return $response;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<?php

namespace Islandora\ResourceService\Provider;

use Silex\Application;
use Silex\ServiceProviderInterface;
use Silex\ControllerProviderInterface;
use Islandora\Chullo\FedoraApi;
use Islandora\Chullo\TriplestoreClient;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Yaml\Yaml;
use Islandora\ResourceService\Controller\ResourceController;

class ResourceServiceProvider implements ServiceProviderInterface, ControllerProviderInterface
{
/**
* Part of ServiceProviderInterface
*/
function register(Application $app) {
//
// Define controller services
//
$app['islandora.resourcecontroller'] = $app->share(function() use ($app) {
return new \Islandora\ResourceService\Controller\ResourceController($app);
});
if (!isset($app['twig'])) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary, I thought the $app->share() took care of this type of logic?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@whikloj, do you mean the "use" statements? I was unsure if putting there had any logic, but i thought it could be a good idea for code clarity/new users. I can remove it once all others had a look at the code

$app['twig'] = $app->share($app->extend('twig', function($twig, $app) {
return $twig;
}));
}
if (!isset($app['api'])) {
$app['api'] = $app->share(function() use ($app) {
return FedoraApi::create($app['config']['islandora']['fedoraProtocol'].'://'.$app['config']['islandora']['fedoraHost'].$app['config']['islandora']['fedoraPath']);
});
}
if (!isset($app['triplestore'])) {
$app['triplestore'] = $app->share(function() use ($app) {
return TriplestoreClient::create($app['config']['islandora']['tripleProtocol'].'://'.$app['config']['islandora']['tripleHost'].$app['config']['islandora']['triplePath']);
});
}
/**
* Ultra simplistic YAML settings loader.
*/
if (!isset($app['config'])) {
$app['config'] = $app->share(function() use ($app){
if ($app['debug']) {
$configFile = __DIR__.'/../../config/settings.dev.yml';
}
else {
$configFile = __DIR__.'/../../config/settings.yml';
}
$settings = Yaml::parse(file_get_contents($configFile));
return $settings;
});
}
/**
* Make our middleware callback functions protected
*/
/**
* before middleware to handle browser requests.
*/
$app['islandora.htmlHeaderToTurtle'] = $app->protect(function(Request $request) {
// In case the request was made by a browser, avoid
// returning the whole Fedora4 API Rest interface page.
if (0 === strpos($request->headers->get('Accept'),'text/html')) {
$request->headers->set('Accept', 'text/turtle', TRUE);
}
});


/**
* before middleware to normalize host header to same as fedora's running
* instance.
*/
$app['islandora.hostHeaderNormalize'] = $app->protect(function(Request $request) use ($app) {
// Normalize Host header to Repo's real location
$request->headers->set('Host', $app['config']['islandora']['fedoraHost'], TRUE);
});

/**
* Converts request $id (uuid) into a fedora4 resourcePath
*/
$app['islandora.idToUri'] = $app->protect(function ($id) use ($app) {
// Run only if $id given /can also be refering root resource,
// we accept only UUID V4 or empty
if (NULL != $id) {
$sparql_query = $app['twig']->render('getResourceByUUIDfromTS.sparql', array(
'uuid' => $id,
));
error_log($sparql_query);
try {
$sparql_result = $app['triplestore']->query($sparql_query);
}
catch (\Exception $e) {
$app->abort(503, 'Chullo says "Triple Store Not available"');
}
// We only assign one in case of multiple ones
// Will have to check for edge cases?
foreach ($sparql_result as $triple) {
return $triple->s->getUri();
}
// Abort the routes if we don't get a subject from the tripple.
$app->abort(404, sprintf('Failed getting resource Path for "%s" from triple store', $id));
}
else {
// If $id is empty then assume we are dealing with fedora base rest endpoint
return $app['config']['islandora']['fedoraProtocol'].'://'.$app['config']['islandora']['fedoraHost'].$app['config']['islandora']['fedoraPath'];
}
});

}

function boot(Application $app) {
}

/**
* Part of ControllerProviderInterface
*/
public function connect(Application $app) {
$controllers = $app['controllers_factory'];
//
// Define routing referring to controller services
//
$controllers->get("/resource/{id}/{child}", "islandora.resourcecontroller:get")
->convert('id', $app['islandora.idToUri'])
->assert('id',$app['config']['islandora']['resourceIdRegex'])
->before($app['islandora.hostHeaderNormalize'])
->before($app['islandora.htmlHeaderToTurtle'])
->value('id',"")
->value('child',"")
->bind('islandora.resourceGet');
$controllers->post("/resource/{id}", "islandora.resourcecontroller:post")
->convert('id', $app['islandora.idToUri'])
->assert('id',$app['config']['islandora']['resourceIdRegex'])
->before($app['islandora.hostHeaderNormalize'])
->before($app['islandora.htmlHeaderToTurtle'])
->value('id',"")
->bind('islandora.resourcePost');
$controllers->put("/resource/{id}/{child}", "islandora.resourcecontroller:put")
->convert('id', $app['islandora.idToUri'])
->assert('id',$app['config']['islandora']['resourceIdRegex'])
->before($app['islandora.hostHeaderNormalize'])
->before($app['islandora.htmlHeaderToTurtle'])
->value('id',"")
->value('child',"")
->bind('islandora.resourcePut');
$controllers->patch("/resource/{id}/{child}", "islandora.resourcecontroller:patch")
->convert('id', $app['islandora.idToUri'])
->assert('id',$app['config']['islandora']['resourceIdRegex'])
->before($app['islandora.hostHeaderNormalize'])
->before($app['islandora.htmlHeaderToTurtle'])
->value('id',"")
->value('child',"")
->bind('islandora.resourcePatch');
$controllers->delete("/resource/{id}/{child}", "islandora.resourcecontroller:delete")
->convert('id', $app['islandora.idToUri'])
->assert('id',$app['config']['islandora']['resourceIdRegex'])
->before($app['islandora.hostHeaderNormalize'])
->before($app['islandora.htmlHeaderToTurtle'])
->value('id',"")
->value('child',"")
->bind('islandora.resourceDelete');
return $controllers;
}
}
63 changes: 63 additions & 0 deletions services/ResourceServiceProvider/src/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

namespace Islandora\ResourceService;

require_once __DIR__.'/../vendor/autoload.php';

use Silex\Application;
use Islandora\Chullo\FedoraApi;
use Islandora\Chullo\TriplestoreClient;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Psr\Http\Message\ResponseInterface;
use Silex\Provider\TwigServiceProvider;
use Islandora\ResourceService\Provider\ResourceServiceProvider;

date_default_timezone_set('UTC');

$app = new Application();

$app['debug'] = true;
$app->register(new \Silex\Provider\ServiceControllerServiceProvider());
$app->register(new \Silex\Provider\TwigServiceProvider(), array(
'twig.path' => __DIR__.'/../templates',
));

$islandoraResourceServiceProvider = new \Islandora\ResourceService\Provider\ResourceServiceProvider;

$app->register($islandoraResourceServiceProvider);
$app->mount("/islandora", $islandoraResourceServiceProvider);

/**
* Convert returned Guzzle responses to Symfony responses, type hinted.
*/
$app->view(function (ResponseInterface $psr7) {
return new Response($psr7->getBody(), $psr7->getStatusCode(), $psr7->getHeaders());
});

$app->after(function (Request $request, Response $response, Application $app) {
// Todo a closing controller, not sure what now but i had an idea.
});
$app->error(function (\Symfony\Component\HttpKernel\Exception\HttpException $e, $code) use ($app){
if ($app['debug']) {
return;
}
return new response(sprintf('Islandora Resource Service exception: %s / HTTP %d response', $e->getMessage(), $code), $code);
});
$app->error(function (\Symfony\Component\HttpKernel\Exception\NotFoundHttpException $e, $code) use ($app){
if ($app['debug']) {
return;
}
//Not sure what the best "verbose" message is
return new response(sprintf('Islandora Resource Service exception: %s / HTTP %d response', $e->getMessage(), $code), $code);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capital R?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right!! moving to uppercase… sorry. Thanks for catching this @daniel-dgi and thanks @whikloj for explaining me what dani catched!! I can't even see my own code sometimes.

});
$app->error(function (\Exception $e, $code) use ($app){
if ($app['debug']) {
return;
}
return new response(sprintf('Islandora Resource Service uncatched exception: %s %d response', $e->getMessage(), $code), $code);
});


$app->run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
PREFIX nfo: <http://www.semanticdesktop.org/ontologies/2007/03/22/nfo/v1.2/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
SELECT ?s WHERE {
?s nfo:uuid "{{uuid}}"^^<http://www.w3.org/2001/XMLSchema#string> .
}