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

Collection Service Implementation #140

Merged
merged 5 commits into from
Feb 16, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
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
23 changes: 9 additions & 14 deletions services/CollectionService/composer.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
{
"name": "islandora/collection-service",
"description": "RESTful service providing resources in Fedora 4",
"repositories": [
{
"type": "vcs",
"url": "https://github.com/Islandora-CLAW/chullo"

},
{
"type": "path",
"url": "/Users/dpino/Desktop/Development/ISLANDORAWORK/CLAW_MICRO/islandora/services/ResourceServiceProvider"
}
],
"description": "RESTful service providing PCDM Collections in Fedora 4",
"repositories": [{
"type": "path",
"url": "../ResourceServiceProvider"
}],
"require": {
"islandora/chullo": "dev-master",
"islandora/chullo": "^0.0",
"islandora/resource-service" : "dev-sprint-002",
"silex/silex": "^1.3",
"symfony/config": "^3.0",
"twig/twig": "^1.23",
"symfony/yaml": "^3.0"
"symfony/yaml": "^3.0",
"easyrdf/easyrdf": "^0.9.1",
"ml/json-ld": "^1.0"
},
"autoload": {
"psr-4": {"Islandora\\CollectionService\\": "src/"}
Expand Down
5 changes: 4 additions & 1 deletion services/CollectionService/config/settings.dev.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# Islandora Dev Settings to be used with $app['debug'] == TRUE
islandora:
apiVersion: 0.0.1
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})?"
resourceIdRegex: "(?:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})?"
# This domain is used as namespace (hashed) when generating UUID V5 identifiers, replace with your own
defaultNamespaceDomainUuuidV5: "www.islandora.ca"
5 changes: 4 additions & 1 deletion services/CollectionService/config/settings.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
islandora:
apiVersion: 0.0.1
fedoraProtocol: http
Copy link
Member

Choose a reason for hiding this comment

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

What is the apiVersion number tied to?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ruebot, just an idea i had. Since our api is tied to branch name right now maybe that numbering makes no sense. Any suggestions? thanks!

Copy link
Member

Choose a reason for hiding this comment

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

No suggestions now. Just curious what it was.

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})?"
resourceIdRegex: "(?:[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})?"
# This domain is used as namespace (hashed) when generating UUID V5 identifiers, replace with your own
defaultNamespaceDomainUuuidV5: "www.islandora.ca"
163 changes: 116 additions & 47 deletions services/CollectionService/src/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,12 @@
require_once __DIR__.'/../vendor/autoload.php';

use Silex\Application;
use Islandora\Chullo\FedoraApi;
use Islandora\Chullo\TriplestoreClient;
use Islandora\ResourceService\Provider\ResourceServiceProvider;
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 Symfony\Component\Yaml\Yaml;

date_default_timezone_set('UTC');

Expand All @@ -25,39 +22,24 @@
'twig.path' => __DIR__.'/../templates',
));

$app['twig'] = $app->share($app->extend('twig', function($twig, $app) {
return $twig;
}));
$islandoraResourceServiceProvider = new \Islandora\ResourceService\Provider\ResourceServiceProvider;

$app->register($islandoraResourceServiceProvider);
//Registers Resource Service and defines current app's path for config context
$app->register($islandoraResourceServiceProvider, array(
'islandora.BasePath' => __DIR__,
));
$app->mount("/islandora", $islandoraResourceServiceProvider);
$app->register(new \Islandora\ResourceService\Provider\UuidServiceProvider(), array(
'UuidServiceProvider.default_namespace' => $app['config']['islandora']['defaultNamespaceDomainUuuidV5'],
));

//$app['uuid'] = $app['islandora.uuid5'](rand());
//We will use a static uuid5 to fake a real IRI to replace <> during turtle parsing
$app['uuid5'] = $app->share(function () use ($app) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Might as well just jam the fake uri here. Or even better, just store it as a variable. No need for it to be in the DI container. When we refactor into a thin controller, the variable should reside within the fat service the controller utilizes.

Copy link
Member

Choose a reason for hiding this comment

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

Not sure I understand what you mean? He is generating the fake URI here using the uuid5 function and storing it in a variable on the app.

We could do it inside the route, but this is good too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@daniel-dgi, UUIDV5 works as a constant (build from, in this case the www.islandora.ca defined the yaml file plus the route to fedora4's endpoint. But it's still an UUID so there is less Chance that people could by mistake put something like this into an input RDF that we could end stripping out and removing in our str_replace function. I could not find a simpler way of putting there something that would work as expected. I can cache this if you want or change the IRI to urn:islandora:theuuid to make it even more failsafe.

Copy link
Contributor

Choose a reason for hiding this comment

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

I understand that you're using this as a dummy value to keep EasyRDF happy. I have no issue with that, nor how you're generating the value. What I'm saying is that you're not using this function anywhere else except to generate the fake id, right? So skip the middle man and just put the fake ID in there.

My second point is to avoid using the DI container as a cache for variables. You'll quickly wind up with a universal resource locator, or god object, not a DI container. You can just make a variable in the index.php scope, or even better, put it in the class we create when refactoring away from a fat controller. This can wait for later, though.

return $app['islandora.uuid5']($app['config']['islandora']['fedoraProtocol'].'://'.$app['config']['islandora']['fedoraHost'].$app['config']['islandora']['fedoraPath']);
});
//This is the uuidV4 generator
$app['uuid'] = $app['islandora.uuid4'];

/**
* Still Not used, this function will check for content type
*/
$isFedora4Content = function (Request $request) use ($app) {
$rdf_content_types = array(
"text/turtle",
"text/rdf+n3",
"application/n3",
"text/n3",
"application/rdf+xml",
"application/n-triples",
"application/sparql-update"
);
if (in_array($request->headers->get('Content-type'), $rdf_content_types)) {
return true;
}
return false;
};

/**
* Convert returned Guzzle responses to Symfony responses.
*/
Expand All @@ -67,40 +49,127 @@
});

/**
* Collection POST route test. Does nothing more than redirect from collection to mounted
* takes 'rx' and/or 'checksum' as optional query arguments
* Collection POST route.
* Takes $id (valid UUID or empty) for the parent resource as first value to match,
* and also takes 'rx' as an optional query argument.
*/
Copy link
Member

Choose a reason for hiding this comment

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

This doc comment is unclear.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not good at comments nor english. I followed the same type of comments i did for resources. Any suggestion on how to document this is appreciated.

Copy link
Member

Choose a reason for hiding this comment

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

How about this?

* Collection POST route. 
* Takes $id (valid UUID or empty) for the parent resource as first value to match, 
* and also takes 'rx' as an optional query argument.

Copy link
Member

Choose a reason for hiding this comment

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

...and your English is fine 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍 , sounds fine. Will update now, thanks!

$app->post("/islandora/collection",function (Application $app, Request $request) {
error_log($app['uuid']);
$tx = $request->query->get('tx', "");
$url = $request->getUriForPath('/islandora/resource/');
error_log($url);
//static public Request create(string $uri, string $method = 'GET', array $parameters = array(), array $cookies = array(), array $files = array(), array $server = array(), string $content = null)
$subRequest = Request::create($url, 'GET', array(), $request->cookies->all(), $request->files->all(), $request->server->all());
error_log($subRequest->__toString());
$response = $app->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false);
return $response;
$app->post("/islandora/collection/{id}", function (Request $request, $id) use ($app) {
$tx = $request->query->get('tx', "");

//Check for format
$format = NULL;
try {
$format = \EasyRdf_Format::getFormat($contentType = $request->headers->get('Content-Type', 'text/turtle'));
} catch (\EasyRdf_Exception $e) {
$app->abort(415, $e->getMessage());
}

//Now check if body can be parsed in that format
if ($format) { //EasyRdf_Format
//@see http://www.w3.org/2011/rdfa-context/rdfa-1.1 for defaults
\EasyRdf_Namespace::set('pcdm', 'http://pcdm.org/models#');
\EasyRdf_Namespace::set('nfo', 'http://www.semanticdesktop.org/ontologies/2007/03/22/nfo/v1.1/');
\EasyRdf_Namespace::set('isl', 'http://www.islandora.ca/ontologies/2016/02/28/isl/v1.0/');
\EasyRdf_Namespace::set('ldp', 'http://www.w3.org/ns/ldp');

//Fake IRI, default LDP one for current resource "<>" is not a valid IRI!
$fakeIri = new \EasyRdf_ParsedUri('urn:uuid:'.$app['uuid5']);

$graph = new \EasyRdf_Graph();
try {
$graph->parse($request->getContent(), $format->getName(), $fakeIri);
} catch (\EasyRdf_Exception $e) {
$app->abort(415, $e->getMessage());
}
//Add a pcmd:Collection type
$graph->resource($fakeIri, 'pcdm:Collection');

//Check if we got an UUID inside posted RDF. We won't validate it here because it's the caller responsability
if (NULL != $graph->countValues($fakeIri, '<http://www.semanticdesktop.org/ontologies/2007/03/22/nfo/v1.1/uuid>')) {
$existingUuid = $graph->getLiteral($fakeIri, '<http://www.semanticdesktop.org/ontologies/2007/03/22/nfo/v1.1/uuid>');
$graph->addResource($fakeIri, 'http://www.islandora.ca/ontologies/2016/02/28/isl/v1.0/hasURN', 'urn:uuid:'.$existingUuid); //Testing an Islandora Ontology!
} else {
//No UUID from the caller in RDF, lets put something there
$tmpUuid = $app['uuid']; //caching here, since it's regenerated each time it is used and I need the same twice
$graph->addLiteral($fakeIri,"http://www.semanticdesktop.org/ontologies/2007/03/22/nfo/v1.1/uuid",$tmpUuid); //Keeps compat for now with other services
$graph->addResource($fakeIri,"http://www.islandora.ca/ontologies/2016/02/28/isl/v1.0/hasURN",'urn:uuid:'.$tmpUuid); //Testing an Islandora Ontology
}
//Restore LDP <> IRI on serialised graph
$pcmd_collection_rdf= str_replace($fakeIri, '', $graph->serialise('turtle'));
}

$urlRoute = $request->getUriForPath('/islandora/resource/');

$subRequestPost = Request::create($urlRoute.$id, 'POST', array(), $request->cookies->all(), array(), $request->server->all(), $pcmd_collection_rdf);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why subrequest when you can just use chullo?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Lots of reasons, we get all the middleware ,like checking if the base resource is there, header rewriting and we don't need to rewrite psr7 responses. More over, the idea is to not use chullo directly. ResourceService should deal with any new chullo functionality if needed (if we for example need to recode) but not in this higher level services. And since we already using all the other services ResourceService provides, going to chullo when we already have the routes is sub-using our code.

$subRequestPost->query->set('tx', $tx);
$subRequestPost->headers->set('Content-Type', 'text/turtle');
$responsePost = $app->handle($subRequestPost, HttpKernelInterface::SUB_REQUEST, false);

if (201 == $responsePost->getStatusCode()) {// OK, collection created
//Lets take the location header in the response
$indirect_container_rdf = $app['twig']->render('createIndirectContainerfromTS.ttl', array(
'resource' => $responsePost->headers->get('location'),
));

$subRequestPut = Request::create($urlRoute.$id, 'PUT', array(), $request->cookies->all(), array(), $request->server->all(), $indirect_container_rdf);
$subRequestPut->query->set('tx', $tx);
$subRequestPut->headers->set('Slug', 'members');
//Can't use in middleware, but needed. Without Fedora 4 throws big java errors!
$subRequestPut->headers->set('Host', $app['config']['islandora']['fedoraHost'], TRUE);
$subRequestPut->headers->set('Content-Type', 'text/turtle');
//Here is the thing. We don't know if UUID of the collection we just created is already in the tripple store.
//So what to do? We could just try to use our routes directly, but UUID check agains triplestore we could fail!
//lets invoke the controller method directly
$responsePut = $app['islandora.resourcecontroller']->put($app, $subRequestPut, $responsePost->headers->get('location'), "members");
if (201 == $responsePut->getStatusCode()) {// OK, indirect container created
//Return only the last created resource
$putHeaders = $responsePut->getHeaders();
//Guzzle psr7 response objects are inmutable. So we have to make this an array and add directly
$putHeaders['Link'] = array('<'.$urlRoute.$tmpUuid.'/members>; rel="alternate"');

return new Response($responsePut->getBody(), 201, $putHeaders);
}

return $responsePut;
}
//Abort if PCDM collection object could not be created
$app->abort($responsePost->getStatusCode(), 'Failed creating PCDM Collection');
})
->value('id',"");

$app->after(function (Request $request, Response $response) use ($app) {
$response->headers->set('X-Powered-By', 'Islandora Collection REST API v'.$app['config']['islandora']['apiVersion'], TRUE); //Nice
Copy link
Member

Choose a reason for hiding this comment

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

I like this 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

gracias!


});

$app->error(function (\Symfony\Component\HttpKernel\Exception\HttpException $e, $code) use ($app){
//Common error Handling
$app->error(function (\EasyRdf_Exception $e, $code) use ($app) {
if ($app['debug']) {
return;
}
return new response(sprintf('Islandora Resource Service exception: %s / HTTP %d response', $e->getMessage(), $code), $code);

return new response(sprintf('RDF Library exception', $e->getMessage(), $code), $code);
});
$app->error(function (\Symfony\Component\HttpKernel\Exception\NotFoundHttpException $e, $code) use ($app){
$app->error(function (\Symfony\Component\HttpKernel\Exception\HttpException $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);

return new response(sprintf('Islandora Collection Service exception: %s / HTTP %d response', $e->getMessage(), $code), $code);
});
$app->error(function (\Exception $e, $code) use ($app){
$app->error(function (\Symfony\Component\HttpKernel\Exception\NotFoundHttpException $e, $code) use ($app) {
if ($app['debug']) {
return;
}
return new response(sprintf('Islandora Resource Service uncatched exception: %s %d response', $e->getMessage(), $code), $code);
}
//Not sure what the best "verbose" message is
return new response(sprintf('Islandora Collection Service exception: %s / HTTP %d response', $e->getMessage(), $code), $code);
});
$app->error(function (\Exception $e, $code) use ($app) {
if ($app['debug']) {
return;
}

return new response(sprintf('Islandora Collection Service uncatched exception: %s %d response', $e->getMessage(), $code), $code);
});

$app->run();

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@prefix ldp: <http://www.w3.org/ns/ldp#> .
@prefix pcdm: <http://pcdm.org/models#> .
@prefix ore: <http://www.openarchives.org/ore/terms/> .

<> a ldp:IndirectContainer ;
ldp:membershipResource <{{ resource }}> ;
ldp:hasMemberRelation pcdm:hasMember ;
ldp:insertedContentRelation ore:proxyFor .

This file was deleted.

8 changes: 1 addition & 7 deletions services/ResourceServiceProvider/composer.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
{
"name": "islandora/resource-service",
"description": "RESTful service providing resources in Fedora 4",
"repositories": [
{
"type": "vcs",
"url": "https://github.com/Islandora-CLAW/chullo"
}
],
"require": {
"islandora/chullo": "dev-master",
"islandora/chullo": "^0.0",
"silex/silex": "^1.3",
"symfony/config": "^3.0",
"twig/twig": "^1.23",
Expand Down
2 changes: 1 addition & 1 deletion services/ResourceServiceProvider/config/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ islandora:
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})?"
# This domain is used as namespace (hashed) when generating UUID V5 identifiers, replace with your own
# This domain is used as namespace (hashed) when generating UUID V5 identifiers, replace with your own
defaultNamespaceDomainUuuidV5: "www.islandora.ca"
Loading