From ceb1576d6d1013dcb9721f6e96eb141e3cc822e7 Mon Sep 17 00:00:00 2001 From: David Cook Date: Fri, 12 May 2017 16:53:47 +1000 Subject: [PATCH] Bug 18585 - Connect to RDF triplestore This commit adds a 'triplestore' method to C4::Context. It takes a model name as a parameter and returns a RDF::Trine::Model object if triplestore.yaml is properly configured. (At the moment, it just supports RDF::Trine::Store::SPARQL, but it would be trivial to add support for RDF::Trine::Store::DBI too, or any other RDF::Trine::Store::* backend.) This code will provide a base from which people can use RDF::Trine for querying and updating a RDF triplestore. --- C4/Context.pm | 64 ++++++++++++++++++++++++++++++++ C4/Installer/PerlDependencies.pm | 5 +++ debian/scripts/koha-create | 4 ++ debian/templates/koha-conf-site.xml.in | 3 ++ debian/templates/triplestore.yaml.in | 30 +++++++++++++++ etc/koha-conf.xml | 3 ++ etc/triplestore.yaml | 30 +++++++++++++++ t/db_dependent/triplestore.t | 67 ++++++++++++++++++++++++++++++++++ 8 files changed, 206 insertions(+) create mode 100644 debian/templates/triplestore.yaml.in create mode 100644 etc/triplestore.yaml create mode 100644 t/db_dependent/triplestore.t diff --git a/C4/Context.pm b/C4/Context.pm index 1eb5f88902..e418fb788e 100644 --- a/C4/Context.pm +++ b/C4/Context.pm @@ -97,6 +97,7 @@ use POSIX (); use DateTime::TimeZone; use Module::Load::Conditional qw(can_load); use Carp; +use YAML; use C4::Boolean; use C4::Debug; @@ -776,6 +777,69 @@ sub restore_dbh # return something, then this function should, too. } +=head2 triplestore + + my $triplestore = C4::Context->triplestore($model_name); + +Returns a handle to an initialised RDF::Trine::Model object + +=cut + +sub triplestore { + my ($self,$name) = @_; + die "Must provide a model name as an argument for the triplestore() method" unless $name; + my $triplestore = ( $context->{triplestore} && $context->{triplestore}->{$name} ) ? $context->{triplestore}->{$name} : undef; + if ( ! $triplestore ){ + my $config_file = $context->config('triplestore_config'); + if ( -f $config_file ){ + my $config = YAML::LoadFile($config_file); + if ($config && $name){ + my $new_triplestore = $self->_new_triplestore($config,$name); + if ( $new_triplestore && $new_triplestore->isa("RDF::Trine::Model") ){ + $triplestore = $new_triplestore; + $context->{triplestore}->{$name} = $new_triplestore; + } + } + } + } + return $triplestore; +} + +=head2 _new_triplestore + +=cut + +sub _new_triplestore { + my ($self, $config,$name) = @_; + if ($config && $name){ + my $models = $config->{models}; + if ($models){ + my $model = $models->{$name}; + if ($model){ + my $module = $model->{module}; + if ($module && $module eq 'RDF::Trine::Store::SPARQL'){ + if ($module && can_load( 'modules' => { $module => undef } ) ){ + my $url = URI->new($model->{url}); + if ($url){ + my $ua = RDF::Trine->default_useragent; + if ($ua){ + my $realm = $model->{realm}; + my $username = $model->{username}; + my $password = $model->{password}; + $ua->credentials($url->host.":".$url->port, $realm, $username, $password); + } + my $store = RDF::Trine::Store::SPARQL->new($url); + my $model = RDF::Trine::Model->new($store); + return $model; + } + } + } + } + } + } + return; +} + =head2 queryparser $queryparser = C4::Context->queryparser diff --git a/C4/Installer/PerlDependencies.pm b/C4/Installer/PerlDependencies.pm index 84ba74d2fc..fbe26bc11b 100644 --- a/C4/Installer/PerlDependencies.pm +++ b/C4/Installer/PerlDependencies.pm @@ -867,6 +867,11 @@ our $PERL_DEPS = { 'required' => '0', 'min_ver' => '0.17', }, + 'RDF::Trine' => { + 'usage' => 'Managing RDF and triplestores', + 'required' => '1', + 'min_ver' => '1.017', + }, }; 1; diff --git a/debian/scripts/koha-create b/debian/scripts/koha-create index aa01f809b7..e6a2b84292 100755 --- a/debian/scripts/koha-create +++ b/debian/scripts/koha-create @@ -743,6 +743,10 @@ eof generate_config_file zebra.passwd.in \ "/etc/koha/sites/$name/zebra.passwd" + # Generate and install triplestore config file + generate_config_file triplestore.yaml.in \ + "/etc/koha/sites/$name/triplestore.yaml" + # Create a GPG-encrypted file for requesting a DB to be set up. if [ "$op" = request ] then diff --git a/debian/templates/koha-conf-site.xml.in b/debian/templates/koha-conf-site.xml.in index 85b54a9ec5..d0ff8f6ed9 100644 --- a/debian/templates/koha-conf-site.xml.in +++ b/debian/templates/koha-conf-site.xml.in @@ -345,5 +345,8 @@ __END_SRU_PUBLICSERVER__ ILLLIBS + + /etc/koha/sites/__KOHASITE__/triplestore.yaml + diff --git a/debian/templates/triplestore.yaml.in b/debian/templates/triplestore.yaml.in new file mode 100644 index 0000000000..39726eaaf5 --- /dev/null +++ b/debian/templates/triplestore.yaml.in @@ -0,0 +1,30 @@ +--- +models: + query: + #module must be a valid RDF::Trine::Store::* module + module: RDF::Trine::Store::SPARQL + + #When using RDF::Trine::Store::SPARQL: + url: + realm: + username: + password: + update: + #module must be a valid RDF::Trine::Store::* module + module: RDF::Trine::Store::SPARQL + + #When using RDF::Trine::Store::SPARQL: + url: + realm: + username: + password: + #NOTE: Only the above models are likely to be used in Koha + read/write: + #module must be a valid RDF::Trine::Store::* module + module: RDF::Trine::Store::SPARQL + + #When using RDF::Trine::Store::SPARQL: + url: + realm: + username: + password: diff --git a/etc/koha-conf.xml b/etc/koha-conf.xml index 268175dc67..b7ebcb5ff1 100644 --- a/etc/koha-conf.xml +++ b/etc/koha-conf.xml @@ -171,5 +171,8 @@ __PAZPAR2_TOGGLE_XML_POST__ ILLLIBS + + __KOHA_CONF_DIR__/triplestore.yaml + diff --git a/etc/triplestore.yaml b/etc/triplestore.yaml new file mode 100644 index 0000000000..39726eaaf5 --- /dev/null +++ b/etc/triplestore.yaml @@ -0,0 +1,30 @@ +--- +models: + query: + #module must be a valid RDF::Trine::Store::* module + module: RDF::Trine::Store::SPARQL + + #When using RDF::Trine::Store::SPARQL: + url: + realm: + username: + password: + update: + #module must be a valid RDF::Trine::Store::* module + module: RDF::Trine::Store::SPARQL + + #When using RDF::Trine::Store::SPARQL: + url: + realm: + username: + password: + #NOTE: Only the above models are likely to be used in Koha + read/write: + #module must be a valid RDF::Trine::Store::* module + module: RDF::Trine::Store::SPARQL + + #When using RDF::Trine::Store::SPARQL: + url: + realm: + username: + password: diff --git a/t/db_dependent/triplestore.t b/t/db_dependent/triplestore.t new file mode 100644 index 0000000000..71665ec7a4 --- /dev/null +++ b/t/db_dependent/triplestore.t @@ -0,0 +1,67 @@ +#!/usr/bin/perl + +use Modern::Perl; +use Test::More tests => 4; +use Test::Exception; +use YAML; +use File::Temp qw/ tempfile /; + +use C4::Context; + +#Make a temporary configuration file for the triplestore +my ($fh, $filename) = tempfile(undef, UNLINK => 1); +my $temp_config = { + models => { + test => { + module => 'RDF::Trine::Store::SPARQL', + url => 'http://localhost/example/rdf', + realm => undef, + username => undef, + password => undef, + } + } +}; + +#Tell C4::Context to use the temporary configuration file as the triplestore_config +my $context = $C4::Context::context; +$context->{config}->{triplestore_config} = $filename; + +subtest 'success' => sub { + YAML::DumpFile($filename, $temp_config); + + my $context_object = C4::Context->new(); + my $triplestore = $context_object->triplestore("test"); + is(ref $triplestore, 'RDF::Trine::Model', 'C4::Context->new()->triplestore returns RDF::Trine::Model if module equals RDF::Trine::Store::SPARQL'); +}; + +subtest 'missing url' => sub { + #Reset triplestore context + delete $context->{triplestore}->{test}; + + my $url = delete $temp_config->{models}->{test}->{url}; + YAML::DumpFile($filename, $temp_config); + + my $context_object = C4::Context->new(); + my $triplestore = $context_object->triplestore("test"); + is($triplestore, undef, 'C4::Context->new()->triplestore returns undef if missing url for SPARQL store'); + + #Restore url for other tests + $temp_config->{url} = $url; +}; + +subtest 'bad module' => sub { + #Reset triplestore context + delete $context->{triplestore}->{test}; + + $temp_config->{module} = 'Local::RDF::Bad::Module'; + YAML::DumpFile($filename, $temp_config); + + my $context_object = C4::Context->new(); + my $triplestore = $context_object->triplestore("test"); + is($triplestore, undef, 'C4::Context->new()->triplestore returns undef if module equals Local::RDF::Bad::Module'); +}; + +subtest 'missing model name' => sub { + my $context_object = C4::Context->new(); + dies_ok { $context_object->triplestore } 'C4::Context::triplestore() method dies if no model name provided'; +}; -- 2.12.3