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

Example ResourceService #131

Merged
merged 15 commits into from
Jan 15, 2016
Merged
Show file tree
Hide file tree
Changes from 13 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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ tests/test_config.ini
install/.vagrant
install/downloads
site/

services/ResourceService/composer.lock
1 change: 1 addition & 0 deletions services/ResourceService/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
vendor/
26 changes: 26 additions & 0 deletions services/ResourceService/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"
Copy link
Member

Choose a reason for hiding this comment

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

Duplicate of below comment: Should this be the main github repo and just make this PR rely on the Chullo PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

completely. Not needed anymore, since we have agreement on chullo. Will change this during the day. Thanks @whikloj

}
],
"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": "[email protected]"
}
]
}
9 changes: 9 additions & 0 deletions services/ResourceService/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/ResourceService/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})?"
250 changes: 250 additions & 0 deletions services/ResourceService/src/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
<?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 Symfony\Component\Yaml\Yaml;

date_default_timezone_set('UTC');

$app = new Application();

$app['debug'] = true;

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


$app['twig'] = $app->share($app->extend('twig', function($twig, $app) {
return $twig;
}));

$app['api'] = $app->share(function() use ($app) {
return FedoraApi::create($app['config']['islandora']['fedoraProtocol'].'://'.$app['config']['islandora']['fedoraHost'].$app['config']['islandora']['fedoraPath']);
});

$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.
*/
$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));
Copy link
Contributor

Choose a reason for hiding this comment

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

So simple. Love it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

😊

return $settings;
});

/**
* before middleware to handle browser requests.
*/
$htmlHeaderToTurtle = 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.
*/
$hostHeaderNormalize = 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
*/
$idToUri = 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,
));
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'];
}

};

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

/**
* Resource GET route. 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
*/
$app->get("/islandora/resource/{id}/{child}",function (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;
})
->value('child',"")
->value('id',"")
->convert('id', $idToUri)
->assert('id',$app['config']['islandora']['resourceIdRegex'])
->before($htmlHeaderToTurtle)
->before($hostHeaderNormalize);

/**
* Resource POST route. 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
*/
$app->post("/islandora/resource/{id}",function (Application $app, Request $request, $id, $checksum) {
$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;
})
->value('id',"")
->assert('id',$app['config']['islandora']['resourceIdRegex'])
->before($htmlHeaderToTurtle)
->before($hostHeaderNormalize);

/**
* 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.
*/
$app->put("/islandora/resource/{id}/{child}",function (Application $app, Request $request, $id, $child) {
Copy link
Member

Choose a reason for hiding this comment

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

Should there also be a PUT route for when you only have an $id? Same for PATCH below and DELETE

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi, child is optional, set to "" by default at https://github.com/Islandora-CLAW/CLAW/pull/131/files#diff-079c3800368dc100b90e9a9254e6fcc6R176 , so if not passed/used in the call it's all good, the route will match. Only post is {$id} only, and i would love to add $child there too

Copy link
Member

Choose a reason for hiding this comment

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

Ok, error was due to me sending my own Content-type header with curl. Future work might be to just strip the duplicate headers for PATCH as it has to be application/sparql-update.

$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;
})
->value('child',"")
->assert('id',$app['config']['islandora']['resourceIdRegex'])
->before($htmlHeaderToTurtle)
->before($hostHeaderNormalize);

/**
* Resource PATCH route. takes $id (valid UUID or empty) for the resource to be modified via SPARQL as first value to match,
* optional a Child resource relative path
* takes 'rx' as optional query argument
* @see https://wiki.duraspace.org/display/FEDORA40/RESTful+HTTP+API#RESTfulHTTPAPI-GreenPATCHModifythetriplesassociatedwitharesourcewithSPARQL-Update
*/
$app->patch("/islandora/resource/{id}/{child}",function (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;
})
->value('child',"")
->assert('id',$app['config']['islandora']['resourceIdRegex'])
->before($htmlHeaderToTurtle)
->before($hostHeaderNormalize);

/**
* Resource DELETE route. takes $id (valid UUID or empty) for the resource to be modified via SPARQL as first value to match,
* optional a Child resource relative path
* takes 'rx' as optional query argument, also 'force' to remove the tombstone in one step
* @see https://wiki.duraspace.org/display/FEDORA40/RESTful+HTTP+API#RESTfulHTTPAPI-RedDELETEDeletearesource
*/
$app->delete("/islandora/resource/{id}/{child}",function (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;
})
->value('child',"")
->assert('id',$app['config']['islandora']['resourceIdRegex'])
->before($htmlHeaderToTurtle)
->before($hostHeaderNormalize);

$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);
});
$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> .
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 could stick here an extra variable, to get also type of resource. This way we can avoid hitting fedora if the send one mismatches the existing one. (it's rdf, we try to put a binary for example).

}
3 changes: 3 additions & 0 deletions services/TransactionService/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
composer.lock
composer.phar
vendor
23 changes: 23 additions & 0 deletions services/TransactionService/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "islandora/transaction-service",
"description": "RESTful service providing transactions in Fedora 4",
"repositories": [
{
"type": "vcs",
"url": "/Users/dpino/Desktop/Development/ISLANDORAWORK/CLAW_MICRO/chullo"
Copy link
Member

Choose a reason for hiding this comment

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

Should this be the main github repo and just make this PR rely on the Chullo PR?

}
],
"require": {
"islandora/chullo": "dev-master",
"silex/silex": "^1.3"
},
"autoload": {
"psr-4": {"Islandora\\TransactionService\\": "src/"}
},
"authors": [
{
"name": "Daniel Lamb",
"email": "[email protected]"
}
]
}
58 changes: 58 additions & 0 deletions services/TransactionService/src/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

namespace Islandora\TransactionService;

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

use GuzzleHttp\Exception\ClientException;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can remove Guzzle here right?

use Islandora\Chullo\FedoraApi;
use Silex\Application;
use Symfony\Component\HttpFoundation\Response;
use Psr\Http\Message\ResponseInterface;

date_default_timezone_set('UTC');

$app = new Application();

$app['debug'] = true;

$app['fedora'] = $app->share(function () {
return FedoraApi::create('http://127.0.0.1:8080/fcrepo/rest');
});

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

$app->post(
"/islandora/transaction",
function (Application $app) {
return $app['fedora']->createTransaction();
}
);

$app->post(
"/islandora/transaction/{id}/extend",
function (Application $app, $id) {
return $app['fedora']->extendTransaction($id);
}
);

$app->post(
"/islandora/transaction/{id}/commit",
function (Application $app, $id) {
return $app['fedora']->commitTransaction($id);
}
);

$app->post(
"/islandora/transaction/{id}/rollback",
function (Application $app, $id) {
return $app['fedora']->rollbackTransaction($id);
}
);

$app->run();