From c4261e4b804a50baa9477d40905f73d1c7a1c844 Mon Sep 17 00:00:00 2001 From: Remington Steed Date: Thu, 2 Nov 2017 15:14:51 +0100 Subject: [PATCH] LP#1729620 Cleanup, fix bugs - Rename DB schema file to follow convention - Remove optional DB commands, and leave them in Release Notes (and possibly add to Official Docs) - Create DB upgrade script - Print optional DB commands using \qecho - Change variable "tcn" to "rec_id" everywhere - Move perl API registration to be immediately after related function - Remove unused parameter from sub oai_list_retrieve() in Application/OAI.pm, and from API calls in WWW/OAI.pm - Fix mislabeled parameter in API doc - Add missing end-comment tags in opensrf.xml.example - Add dependency to Ubuntu/Debian makefiles - Add missing init handler in apache/eg.conf.in - Fix reference to sysconfdir in apache/eg_startup.in - Undo extraneous change to .gitignore - Trim/rename release notes, since most of the install process is handled via Evergreen install instructions. Signed-off-by: Remington Steed Signed-off-by: Jane Sandberg Signed-off-by: Galen Charlton Signed-off-by: Mike Rylander --- .gitignore | 1 - Open-ILS/examples/fm_IDL.xml | 4 +- Open-ILS/examples/opensrf.xml.example | 2 + .../src/extras/install/Makefile.debian-buster | 1 + .../extras/install/Makefile.debian-stretch | 1 + .../perlmods/lib/OpenILS/Application/OAI.pm | 95 ++-- Open-ILS/src/perlmods/lib/OpenILS/WWW/OAI.pm | 16 +- Open-ILS/src/sql/Pg/600.schema.oai.sql | 25 ++ Open-ILS/src/sql/Pg/oai.sql | 68 --- .../sql/Pg/upgrade/XXXX.schema.oai_views.sql | 79 ++++ docs/RELEASE_NOTES_NEXT/OAI2/install.adoc | 408 ------------------ .../OAI2/new_oai_opensrf_service.adoc | 160 +++++++ 12 files changed, 323 insertions(+), 537 deletions(-) create mode 100644 Open-ILS/src/sql/Pg/600.schema.oai.sql delete mode 100644 Open-ILS/src/sql/Pg/oai.sql create mode 100644 Open-ILS/src/sql/Pg/upgrade/XXXX.schema.oai_views.sql delete mode 100644 docs/RELEASE_NOTES_NEXT/OAI2/install.adoc create mode 100644 docs/RELEASE_NOTES_NEXT/OAI2/new_oai_opensrf_service.adoc diff --git a/.gitignore b/.gitignore index b49215d978..23d7d99b32 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ *.pyc *.slo *.class -.idea/ aclocal.m4 autom4te.cache/ diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index 4880eeec7a..878ea54e27 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -79,7 +79,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA oils_persist:readonly="true" reporter:core="false" reporter:label="OAI2 record list" oils_persist:tablename="oai.biblio"> - + @@ -89,7 +89,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA oils_persist:readonly="true" reporter:core="false" reporter:label="OAI2 record list" oils_persist:tablename="oai.authority"> - + diff --git a/Open-ILS/examples/opensrf.xml.example b/Open-ILS/examples/opensrf.xml.example index 33e14e7d10..a76e65b521 100644 --- a/Open-ILS/examples/opensrf.xml.example +++ b/Open-ILS/examples/opensrf.xml.example @@ -1087,6 +1087,7 @@ vim:et:ts=4:sw=4: @@ -1094,6 +1095,7 @@ vim:et:ts=4:sw=4: diff --git a/Open-ILS/src/extras/install/Makefile.debian-buster b/Open-ILS/src/extras/install/Makefile.debian-buster index 9d53ca6d15..68b9480450 100644 --- a/Open-ILS/src/extras/install/Makefile.debian-buster +++ b/Open-ILS/src/extras/install/Makefile.debian-buster @@ -42,6 +42,7 @@ export DEBS = \ libexcel-writer-xlsx-perl\ libgd-graph3d-perl\ libgeo-coder-osm-perl\ + libhttp-oai-perl\ liblocale-maketext-lexicon-perl\ liblog-log4perl-perl\ libmarc-charset-perl \ diff --git a/Open-ILS/src/extras/install/Makefile.debian-stretch b/Open-ILS/src/extras/install/Makefile.debian-stretch index f8023141b8..d242aa64cc 100644 --- a/Open-ILS/src/extras/install/Makefile.debian-stretch +++ b/Open-ILS/src/extras/install/Makefile.debian-stretch @@ -42,6 +42,7 @@ export DEBS = \ libexcel-writer-xlsx-perl\ libgd-graph3d-perl\ libgeo-coder-osm-perl\ + libhttp-oai-perl\ liblocale-maketext-lexicon-perl\ liblog-log4perl-perl\ libmarc-charset-perl \ diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/OAI.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/OAI.pm index c4fb61a8ef..3605167274 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/OAI.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/OAI.pm @@ -161,7 +161,7 @@ sub oai_biblio_retrieve { my $self = shift; my $client = shift; - my $tcn = shift; + my $rec_id = shift; my $metadataPrefix = shift; # holdings hold an array of call numbers, which hold an array of copies @@ -173,7 +173,7 @@ sub oai_biblio_retrieve { # Retrieve the bibliographic record and it's copies my $tree = $_storage->request( "open-ils.cstore.direct.biblio.record_entry.retrieve", - $tcn, + $rec_id, { flesh => 5, flesh_fields => { bre => [qw/marc edit_date call_numbers/], @@ -190,7 +190,7 @@ sub oai_biblio_retrieve { my %serials; if ( substr($marc->leader, 7, 1) eq 's' ) { # serial my $_search = OpenSRF::AppSession->create( 'open-ils.search' ); - my $_serials = $_search->request('open-ils.search.serial.record.bib.retrieve', $tcn, 1, 0)->gather(1); + my $_serials = $_search->request('open-ils.search.serial.record.bib.retrieve', $rec_id, 1, 0)->gather(1); my $order = 0 ; for my $sre (@$_serials) { if ( $sre->location ) { @@ -269,7 +269,7 @@ sub oai_biblio_retrieve { $marc->delete_field($_) for ($marc->field('001')); if (!$marc->field('001')) { $marc->insert_fields_ordered( - MARC::Field->new( '001', $tcn ) + MARC::Field->new( '001', $rec_id ) ); } @@ -302,6 +302,36 @@ sub oai_biblio_retrieve { } +__PACKAGE__->register_method( + method => 'oai_biblio_retrieve', + api_name => 'open-ils.oai.biblio.retrieve', + api_level => 1, + argc => 1, + signature => + { + desc => 'Returns the MARCXML representation of the requested bibliographic record.', + params => + [ + { + name => 'rec_id', + desc => 'An OpenILS biblio::record_entry id.', + type => 'number' + }, + { + name => 'metadataPrefix', + desc => 'The metadataPrefix of the schema.', + type => 'string' + } + ], + 'return' => + { + desc => 'An string of the XML in the desired schema.', + type => 'string' + } + } +); + + sub most_recent_date { my $date1 = substr(shift, 0, 19) ; # e.g. '2001-02-03T04:05:06+0000' becomes '2001-02-03T04:05:06' @@ -338,47 +368,18 @@ sub _cp_is_visible { return $visible; } -__PACKAGE__->register_method( - method => 'oai_biblio_retrieve', - api_name => 'open-ils.oai.biblio.retrieve', - api_level => 1, - argc => 1, - signature => - { - desc => 'Returns the MARCXML representation of the requested bibliographic record.', - params => - [ - { - name => 'tcn', - desc => 'An OpenILS biblio::record_entry id.', - type => 'number' - }, - { - name => 'metadataPrefix', - desc => 'The metadataPrefix of the schema.', - type => 'string' - } - ], - 'return' => - { - desc => 'An string of the XML in the desired schema.', - type => 'string' - } - } -); - sub oai_authority_retrieve { my $self = shift; my $client = shift; - my $tcn = shift; + my $rec_id = shift; my $metadataPrefix = shift; my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' ); # Retrieve the authority record - my $record = $_storage->request('open-ils.cstore.direct.authority.record_entry.retrieve', $tcn)->gather(1); + my $record = $_storage->request('open-ils.cstore.direct.authority.record_entry.retrieve', $rec_id)->gather(1); my $o = Fieldmapper::authority::record_entry->new($record) ; my $marc = MARC::Record->new_from_xml( $o->marc, 'UTF8', 'XML'); @@ -386,7 +387,7 @@ sub oai_authority_retrieve { $marc->delete_field($_) for ($marc->field('001')); if (!$marc->field('001')) { $marc->insert_fields_ordered( - MARC::Field->new( '001', $tcn ) + MARC::Field->new( '001', $rec_id ) ); } @@ -409,7 +410,7 @@ __PACKAGE__->register_method( params => [ { - name => 'tcn', + name => 'rec_id', desc => 'An OpenILS authority::record_entry id.', type => 'number' }, @@ -433,16 +434,15 @@ sub oai_list_retrieve { my $self = shift; my $client = shift; my $record_class = shift || 'biblio'; - my $tcn = shift || 0; + my $rec_id = shift || 0; my $from = shift; my $until = shift; my $set = shift ; - my $metadataPrefix = shift; my $max_count = shift; my $deleted_record = shift || 'yes'; my $query = {}; - $query->{'tcn'} = ($max_count eq 1) ? $tcn : {'>=' => $tcn} ; + $query->{'rec_id'} = ($max_count eq 1) ? $rec_id : {'>=' => $rec_id} ; $query->{'set_spec'} = $set if ( $set ); # unsupported $query->{'deleted'} = 'f' unless ( $deleted_record eq 'yes' ); $query->{'datestamp'} = {'>=', $from} if ( $from && !$until ) ; @@ -473,8 +473,8 @@ __PACKAGE__->register_method( desc => '\'biblio\' for bibliographic records or \'authority\' for authority records', type => 'string' }, { - name => 'tcn', - desc => 'An optional tcn number used as a cursor.', + name => 'rec_id', + desc => 'An optional rec_id number used as a cursor.', type => 'number' }, { @@ -493,13 +493,8 @@ __PACKAGE__->register_method( type => 'string' }, { - name => 'metadataPrefix', - desc => 'The metadataPrefix of the schema.', - type => 'string' - }, - { - name => 'offset', - desc => 'The start of the cursor position in the result set.', + name => 'max_count', + desc => 'The number of identifiers to return.', type => 'number' }, { @@ -517,4 +512,4 @@ __PACKAGE__->register_method( ); -1; \ No newline at end of file +1; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/OAI.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/OAI.pm index 7fc5f36ab3..448847e9bc 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/OAI.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/OAI.pm @@ -281,10 +281,10 @@ sub getRecord { # Do we have a valid identifier ? my $regex_identifier = "^${scheme}${delimiter}${repository_identifier}${delimiter}([0-9]+)\$"; if ( $identifier =~ /$regex_identifier/i ) { - my $tcn = $1 ; + my $rec_id = $1 ; # Do we have a record ? - my $record = $oai->request('open-ils.oai.list.retrieve', $record_class, $tcn, undef, undef, undef, $metadataPrefix, 1, $deleted_record)->gather(1) ; + my $record = $oai->request('open-ils.oai.list.retrieve', $record_class, $rec_id, undef, undef, undef, 1, $deleted_record)->gather(1) ; if (@$record) { $response = HTTP::OAI::GetRecord->new(); my $o = "Fieldmapper::oai::$record_class"->new(@$record[0]); @@ -311,14 +311,14 @@ sub listIdentifiers { my ($record_class, $requestURL, $from, $until, $set, $metadataPrefix, $offset ) = @_; my $response; - my $r = $oai->request('open-ils.oai.list.retrieve', $record_class, $offset, $from, $until, $set, $metadataPrefix, $max_count, $deleted_record)->gather(1) ; + my $r = $oai->request('open-ils.oai.list.retrieve', $record_class, $offset, $from, $until, $set, $max_count, $deleted_record)->gather(1) ; if (@$r) { my $cursor = 0 ; $response = HTTP::OAI::ListIdentifiers->new(); for my $record (@$r) { my $o = "Fieldmapper::oai::$record_class"->new($record) ; if ( $cursor++ == $max_count ) { - my $token = new HTTP::OAI::ResumptionToken( resumptionToken => encode_base64(join( '$', $metadataPrefix, $from, $until, $oai_sets->{$set}->{setSpec}, $o->tcn ), '' ) ) ; + my $token = new HTTP::OAI::ResumptionToken( resumptionToken => encode_base64(join( '$', $metadataPrefix, $from, $until, $oai_sets->{$set}->{setSpec}, $o->rec_id ), '' ) ) ; $token->cursor($offset); $response->resumptionToken($token) ; } else { @@ -340,14 +340,14 @@ sub listRecords { my ($record_class, $requestURL, $from, $until, $set, $metadataPrefix, $offset ) = @_; my $response; - my $r = $oai->request('open-ils.oai.list.retrieve', $record_class, $offset, $from, $until, $set, $metadataPrefix, $max_count, $deleted_record)->gather(1) ; + my $r = $oai->request('open-ils.oai.list.retrieve', $record_class, $offset, $from, $until, $set, $max_count, $deleted_record)->gather(1) ; if (@$r) { my $cursor = 0 ; $response = HTTP::OAI::ListRecords->new(); for my $record (@$r) { my $o = "Fieldmapper::oai::$record_class"->new($record) ; if ( $cursor++ == $max_count ) { - my $token = new HTTP::OAI::ResumptionToken( resumptionToken => encode_base64(join( '$', $metadataPrefix, $from, $until, $oai_sets->{$set}->{setSpec}, $o->tcn ), '' ) ) ; + my $token = new HTTP::OAI::ResumptionToken( resumptionToken => encode_base64(join( '$', $metadataPrefix, $from, $until, $oai_sets->{$set}->{setSpec}, $o->rec_id ), '' ) ) ; $token->cursor($offset); $response->resumptionToken($token) ; } else { @@ -378,7 +378,7 @@ sub _header { } return new HTTP::OAI::Header( - identifier => $scheme . $delimiter . $repository_identifier . $delimiter . $o->tcn, + identifier => $scheme . $delimiter . $repository_identifier . $delimiter . $o->rec_id, datestamp => substr($o->datestamp, 0, 19) . 'Z', status => $status, setSpec => \@set_spec @@ -395,7 +395,7 @@ sub _record { if ( $o->deleted eq 'f' ) { my $md = new HTTP::OAI::Metadata() ; - my $xml = $oai->request('open-ils.oai.' . $record_class . '.retrieve', $o->tcn, $metadataPrefix)->gather(1) ; + my $xml = $oai->request('open-ils.oai.' . $record_class . '.retrieve', $o->rec_id, $metadataPrefix)->gather(1) ; $md->dom( $parser->parse_string('' . $xml . '') ); # Not sure why I need to add the metadata element, $record->metadata( $md ); # because I expect ->metadata() would provide the wrapper for it. } diff --git a/Open-ILS/src/sql/Pg/600.schema.oai.sql b/Open-ILS/src/sql/Pg/600.schema.oai.sql new file mode 100644 index 0000000000..1b5f57092f --- /dev/null +++ b/Open-ILS/src/sql/Pg/600.schema.oai.sql @@ -0,0 +1,25 @@ +-- VIEWS for the oai service +CREATE SCHEMA oai; + +-- The view presents a lean table with unique bre.tc-numbers for oai paging; +CREATE VIEW oai.biblio AS + SELECT + bre.id AS rec_id, + bre.edit_date AS datestamp, + bre.deleted AS deleted + FROM + biblio.record_entry bre + ORDER BY + bre.id; + +-- The view presents a lean table with unique are.tc-numbers for oai paging; +CREATE VIEW oai.authority AS + SELECT + are.id AS rec_id, + are.edit_date AS datestamp, + are.deleted AS deleted + FROM + authority.record_entry AS are + ORDER BY + are.id; + diff --git a/Open-ILS/src/sql/Pg/oai.sql b/Open-ILS/src/sql/Pg/oai.sql deleted file mode 100644 index 64688b9dac..0000000000 --- a/Open-ILS/src/sql/Pg/oai.sql +++ /dev/null @@ -1,68 +0,0 @@ --- VIEWS for the oai service -CREATE SCHEMA oai; - --- The view presents a lean table with unique bre.tc-numbers for oai paging; -CREATE VIEW oai.biblio AS - SELECT - bre.id AS tcn, - bre.edit_date AS datestamp, - bre.deleted AS deleted - FROM - biblio.record_entry bre - ORDER BY - bre.id; - --- The view presents a lean table with unique are.tc-numbers for oai paging; -CREATE VIEW oai.authority AS - SELECT - are.id AS tcn, - are.edit_date AS datestamp, - are.deleted AS deleted - FROM - authority.record_entry AS are - ORDER BY - are.id; - --- If an edit date changes in the asset.call_number or asset.copy and you want this to persist to an OAI2 datestamp, --- then add these stored procedures and triggers: -CREATE OR REPLACE FUNCTION oai.datestamp(rid BIGINT) - RETURNS VOID AS $$ -BEGIN - UPDATE biblio.record_entry AS bre - SET edit_date = now() - WHERE bre.id = rid; -END -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION oai.call_number_datestamp() - RETURNS TRIGGER AS $$ -BEGIN - IF TG_OP = 'DELETE' - THEN - PERFORM oai.datestamp(OLD.record); - RETURN OLD; - END IF; - - PERFORM oai.datestamp(NEW.record); - RETURN NEW; - -END -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION oai.copy_datestamp() - RETURNS TRIGGER AS $$ -BEGIN - IF TG_OP = 'DELETE' - THEN - PERFORM oai.datestamp((SELECT acn.record FROM asset.call_number as acn WHERE acn.id = OLD.call_number)); - RETURN OLD; - END IF; - - PERFORM oai.datestamp((SELECT acn.record FROM asset.call_number as acn WHERE acn.id = NEW.call_number)); - RETURN NEW; - -END -$$ LANGUAGE plpgsql; - -CREATE TRIGGER call_number_datestamp AFTER INSERT OR UPDATE OR DELETE ON asset.call_number FOR EACH ROW EXECUTE PROCEDURE oai.call_number_datestamp(); -CREATE TRIGGER copy_datestamp AFTER INSERT OR UPDATE OR DELETE ON asset.copy FOR EACH ROW EXECUTE PROCEDURE oai.copy_datestamp(); \ No newline at end of file diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.oai_views.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.oai_views.sql new file mode 100644 index 0000000000..ef89efc925 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.oai_views.sql @@ -0,0 +1,79 @@ +BEGIN; + +SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version); + +-- VIEWS for the oai service +CREATE SCHEMA oai; + +-- The view presents a lean table with unique bre.tc-numbers for oai paging; +CREATE VIEW oai.biblio AS + SELECT + bre.id AS rec_id, + bre.edit_date AS datestamp, + bre.deleted AS deleted + FROM + biblio.record_entry bre + ORDER BY + bre.id; + +-- The view presents a lean table with unique are.tc-numbers for oai paging; +CREATE VIEW oai.authority AS + SELECT + are.id AS rec_id, + are.edit_date AS datestamp, + are.deleted AS deleted + FROM + authority.record_entry AS are + ORDER BY + are.id; + + +-- OPTIONAL PORTION + +\qecho If an edit date changes in the asset.call_number or asset.copy and you want this to persist to an OAI2 datestamp, +\qecho then add these stored procedures and triggers: +\qecho +\qecho 'CREATE OR REPLACE FUNCTION oai.datestamp(rid BIGINT)' +\qecho ' RETURNS VOID AS $$' +\qecho 'BEGIN' +\qecho ' UPDATE biblio.record_entry AS bre' +\qecho ' SET edit_date = now()' +\qecho ' WHERE bre.id = rid;' +\qecho 'END' +\qecho '$$ LANGUAGE plpgsql;' +\qecho +\qecho 'CREATE OR REPLACE FUNCTION oai.call_number_datestamp()' +\qecho ' RETURNS TRIGGER AS $$' +\qecho 'BEGIN' +\qecho ' IF TG_OP = ''DELETE''' +\qecho ' THEN' +\qecho ' PERFORM oai.datestamp(OLD.record);' +\qecho ' RETURN OLD;' +\qecho ' END IF;' +\qecho +\qecho ' PERFORM oai.datestamp(NEW.record);' +\qecho ' RETURN NEW;' +\qecho +\qecho 'END' +\qecho '$$ LANGUAGE plpgsql;' +\qecho +\qecho 'CREATE OR REPLACE FUNCTION oai.copy_datestamp()' +\qecho ' RETURNS TRIGGER AS $$' +\qecho 'BEGIN' +\qecho ' IF TG_OP = ''DELETE''' +\qecho ' THEN' +\qecho ' PERFORM oai.datestamp((SELECT acn.record FROM asset.call_number as acn WHERE acn.id = OLD.call_number));' +\qecho ' RETURN OLD;' +\qecho ' END IF;' +\qecho +\qecho ' PERFORM oai.datestamp((SELECT acn.record FROM asset.call_number as acn WHERE acn.id = NEW.call_number));' +\qecho ' RETURN NEW;' +\qecho +\qecho 'END' +\qecho '$$ LANGUAGE plpgsql;' +\qecho +\qecho 'CREATE TRIGGER call_number_datestamp AFTER INSERT OR UPDATE OR DELETE ON asset.call_number FOR EACH ROW EXECUTE PROCEDURE oai.call_number_datestamp();' +\qecho 'CREATE TRIGGER copy_datestamp AFTER INSERT OR UPDATE OR DELETE ON asset.copy FOR EACH ROW EXECUTE PROCEDURE oai.copy_datestamp();' + +COMMIT; + diff --git a/docs/RELEASE_NOTES_NEXT/OAI2/install.adoc b/docs/RELEASE_NOTES_NEXT/OAI2/install.adoc deleted file mode 100644 index e54eb3a512..0000000000 --- a/docs/RELEASE_NOTES_NEXT/OAI2/install.adoc +++ /dev/null @@ -1,408 +0,0 @@ -= oai-openils is an openSRF service - -This module is an optional service that exposes your catalog through the [OAI2 protocol](http://www.openarchives.org/OAI/openarchivesprotocol.html). - -== 1. Intended behaviour - -=== 1.1 Entry points -There are two: one for bibliographic records and one for authority records: - - http://your-domain/opac/extras/oai/authority - http://your-domain/opac/extras/oai/biblio - -=== 1.2 Setspec are not implemented - -This is a work in progress and not enabled. The aim is to have the owning library determine the set hierarchy. The Concerto -test database for example has a record with tcn #1. This record is so popular it has copies attached to library units -"Example Branch 1", "Example Branch 2", "Example Branch 3", "Example Bookmobile 1" which is a child of Branch 3 and -"Example Branch 4". This entire kinship is expressed as sets like so: - -```xml -
- ... - CONS - CONS:SYS1 - CONS:SYS2 - CONS:SYS1:BR1 - CONS:SYS1:BR2 - CONS:SYS2:BR3 - CONS:SYS2:BR4 - CONS:SYS2:BR3:BM1 -
-``` -Likewise the setSpecs of authority records are derived from their browse axis ( Title, Author, Subject and Topic ). - -=== 1.3 OAI2 datestamp - -The edit date of the bibliographic and authority record is used as datestamp. If you want the date for editorial updates -of bibliographic assets ( copies, call numbers ) reflected in the datestamp, then add the triggers shown below. - -=== 1.4 Bibliographic mapping of assets to 852 subfields - -Certain attributes asset are placed into 852 subfields so: - -| subfield code | asset resource | -| --- | --- | -| a | location | -| b | owning_lib | -| c | callnumber | -| d | circlib | -| g | barcode | -| n | status | - -Thus the Concerto with tcn #1 will have it's 852 subfields expressed as: -```xml - - Stacks - BR4 - ML 60 R100 - BR4 - CONC70000435 - Checked out - -``` -This mapping can be customized and extended with static subfields: -```xml - A constant value -``` - -=== 1.5 Default configuration - -All default configuration is commented in the open-ils.oai app_settings element. See below for details on how to -override defaults by removing the comments and substitute the values. - -== 2. Installation - -=== 2.1 Perl modules - -Lookup the Perl handler and the associated openils module: - - - [Open-ILS/src/perlmods/lib/OpenILS/WWW/OAI.pm](Open-ILS/src/perlmods/lib/OpenILS/WWW/OAI.pm) - - [Open-ILS/src/perlmods/lib/OpenILS/Application/OAI.pm](Open-ILS/src/perlmods/lib/OpenILS/Application/OAI.pm) - -Place them in your codebase next to the other openils modules and let them thus become part of the build: - - Open-ILS/src/perlmods/lib/OpenILS/Application/OAI.pm - Open-ILS/src/perlmods/lib/OpenILS/WWW/OAI.pm - -or copy the files (owned by the opensrf user) on your servers that host the openils services in the Perl library path: - - /the perl library path/OpenILS/Application/OAI.pm - /the perl library path/OpenILS/WWW/OAI.pm - -=== 2.2 Declare the perl handler - -Declare the Perl handler in the Apache eg_startup file: - -```perl -use OpenILS::WWW::OAI qw( conf/opensrf_core.xml ); -``` - -And reference it in the Apache eg_vhost.conf file, apache 2.2: - - - SetHandler perl-script - PerlHandler OpenILS::WWW::OAI - Options +ExecCGI - PerlSendHeader On - allow from all - - -or apache 2.4 - - - SetHandler perl-script - PerlHandler OpenILS::WWW::OAI - Options +ExecCGI - PerlSendHeader On - Require all granted - - -In the eg.conf file under 'PerlRequire /etc/apache2/eg_startup' add: -```apache -PerlChildInitHandler OpenILS::WWW::OAI::child_init - -``` - -=== 2.3 The database and fieldmapper - -==== 2.3.1 Database - -The service requires a view and stored procedures: Open-ILS/src/sql/Pg/oai.sql - -Add the oai section to the database: -```sql --- VIEWS for the oai service -CREATE SCHEMA oai; - - --- The view presents a lean table with unique bre.tc-numbers for oai paging; -CREATE VIEW oai.biblio AS - SELECT - bre.id AS tcn, - bre.edit_date AS datestamp, - bre.deleted AS deleted - FROM - biblio.record_entry bre - ORDER BY - bre.id; - --- The view presents a lean table with unique are.tc-numbers for oai paging; -CREATE VIEW oai.authority AS - SELECT - are.id AS tcn, - are.edit_date AS datestamp, - are.deleted AS deleted - FROM - authority.record_entry AS are - ORDER BY - are.id; -``` - -==== 2.3.2 Optional, setting the datestamp - -If you want the OAI2 datestamp to reflect changes in assets as well, add the following triggers - ```sql - --- If an edit date changes in the asset.call_number or asset.copy and you want this to persist to an OAI2 datestamp, --- then add these stored procedures and triggers: -CREATE OR REPLACE FUNCTION oai.datestamp(rid BIGINT) - RETURNS VOID AS $$ -BEGIN - UPDATE biblio.record_entry AS bre - SET edit_date = now() - WHERE bre.id = rid; -END -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION oai.call_number_datestamp() - RETURNS TRIGGER AS $$ -BEGIN - IF TG_OP = 'DELETE' - THEN - PERFORM oai.datestamp(OLD.record); - RETURN OLD; - END IF; - - PERFORM oai.datestamp(NEW.record); - RETURN NEW; - -END -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION oai.copy_datestamp() - RETURNS TRIGGER AS $$ -BEGIN - IF TG_OP = 'DELETE' - THEN - PERFORM oai.datestamp((SELECT acn.record FROM asset.call_number as acn WHERE acn.id = OLD.call_number)); - RETURN OLD; - END IF; - - PERFORM oai.datestamp((SELECT acn.record FROM asset.call_number as acn WHERE acn.id = NEW.call_number)); - RETURN NEW; - -END -$$ LANGUAGE plpgsql; - -CREATE TRIGGER call_number_datestamp AFTER INSERT OR UPDATE OR DELETE ON asset.call_number FOR EACH ROW EXECUTE PROCEDURE oai.call_number_datestamp(); -CREATE TRIGGER copy_datestamp AFTER INSERT OR UPDATE OR DELETE ON asset.copy FOR EACH ROW EXECUTE PROCEDURE oai.copy_datestamp(); - ``` - -==== 2.3.3 The fieldmapper - -Proceed by declaring the views in the fm_IDL.xml file so, as the example shows here [Open-ILS/examples/fm_ILD.xml](Open-ILS/examples/fm_IDL.xml): - -```xml - - - - - - - - - - - - - - - - -``` - -=== 2.4 The xslt stylesheets - -Lookup the two documents here: - - - [Open-ILS/xsl/OAI2_OAIDC.xsl](Open-ILS/xsl/OAI2_OAIDC.xsl) - - [Open-ILS/xsl/OAI2_MARC21slim.xsl](Open-ILS/xsl/OAI2_MARC21slim.xsl) - -Place the stylesheets in your codebase next to the other xsl documents and let them thus become part of the build. -Or install them on your servers that host the openils services: - - //var/xsl/OAI2_OAIDC.xsl - //var/xsl/OAI2_MARC21slim.xsl - -=== 2.5 Dependencies -The openils-oai service depends on a running openils-supercat service. -And the OAI2_OAIDC.xsl document uses the file [MARC21slim2OAIDC.xsl](Open-ILS/xsl/MARC21slim2OAIDC.xsl). -The service and stylesheet are part of the out-of-the-box Evergreen distributions. - -But do install the ['HTTP::OAI' perl library from a CPAN repository](http://search.cpan.org/dist/HTTP-OAI/): - - $ cpan HTTP::OAI - - -== 3. Configuration - -=== 3.1 Declare the service - -Add the openils-oai service to your /<openils sysdir>/conf/opensrf.xml file. -```xml -.... - - 5 - 1 - perl - OpenILS::Application::OAI - 199 - - open-ils.oai_unix.sock - open-ils.oai_unix.pid - 1000 - open-ils.oai_unix.log - 1 - 5 - 1 - 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` - -==== 3.2 Activate the service - -Refer to the service in the opensrf.xml's activeapps element: -```xml -.... - - open-ils.oai -``` - -==== 3.3 Register the service with the router - -Add the service to the public router with your /<openils sysdir>/conf/opensrf_core.xml -```xml - - - - - router - public.realm - - openils.oai - ... -``` - - - - - - diff --git a/docs/RELEASE_NOTES_NEXT/OAI2/new_oai_opensrf_service.adoc b/docs/RELEASE_NOTES_NEXT/OAI2/new_oai_opensrf_service.adoc new file mode 100644 index 0000000000..495ba5fe5e --- /dev/null +++ b/docs/RELEASE_NOTES_NEXT/OAI2/new_oai_opensrf_service.adoc @@ -0,0 +1,160 @@ +New OAI Service +^^^^^^^^^^^^^^^ + +This module is an optional service that exposes your catalog through the [OAI2 protocol](http://www.openarchives.org/OAI/openarchivesprotocol.html). + + +Entry points +++++++++++++ +There are two: one for bibliographic records and one for authority records: + + http://your-domain/opac/extras/oai/authority + http://your-domain/opac/extras/oai/biblio + +An example of a working URL on a system with an authority record with ID +1: + + http://your-domain/opac/extras/oai/authority?verb=GetRecord&identifier=oai:localhost:1&metadataPrefix=oai_dc + +Setspec are not implemented ++++++++++++++++++++++++++++ + +This is a work in progress and not enabled. The aim is to have the owning library determine the set hierarchy. The Concerto +test database for example has a record with record ID #1. This record is so popular it has copies attached to library units +"Example Branch 1", "Example Branch 2", "Example Branch 3", "Example Bookmobile 1" which is a child of Branch 3 and +"Example Branch 4". This entire kinship is expressed as sets like so: + +```xml +
+ ... + CONS + CONS:SYS1 + CONS:SYS2 + CONS:SYS1:BR1 + CONS:SYS1:BR2 + CONS:SYS2:BR3 + CONS:SYS2:BR4 + CONS:SYS2:BR3:BM1 +
+``` +Likewise the setSpecs of authority records are derived from their browse axis ( Title, Author, Subject and Topic ). + +Bibliographic mapping of assets to 852 subfields +++++++++++++++++++++++++++++++++++++++++++++++++ + +Certain attributes asset are placed into 852 subfields so: + +|=== +| subfield code | asset resource + +| a | location +| b | owning_lib +| c | callnumber +| d | circlib +| g | barcode +| n | status +|=== + +Thus the Concerto with record ID #1 will have it's 852 subfields expressed as: +```xml + + Stacks + BR4 + ML 60 R100 + BR4 + CONC70000435 + Checked out + +``` +This mapping can be customized and extended with static subfields: +```xml + A constant value +``` + +Default configuration ++++++++++++++++++++++ + +See comments in opensrf.xml (in the open-ils.oai app_settings element) +for default configuration and customization instructions. is commented +in the open-ils.oai app_settings element. + +Upgrade Instructions +++++++++++++++++++++ + +**Activate the service** + +Refer to the service in the opensrf.xml activeapps element: +```xml +.... + + open-ils.oai +``` + +**Register the service with the router** + +Add the service to the public router in your opensrf_core.xml +```xml + + + + + router + public.realm + + openils.oai + ... +``` + +Optional: Setting the datestamp ++++++++++++++++++++++++++++++++ + +The edit date of the bibliographic and authority record is used as +datestamp. If you want the date for editorial updates of bibliographic +assets (i.e. copies, call numbers) reflected in the datestamp, then add the +triggers shown below. + +```sql + +CREATE OR REPLACE FUNCTION oai.datestamp(rid BIGINT) + RETURNS VOID AS $$ +BEGIN + UPDATE biblio.record_entry AS bre + SET edit_date = now() + WHERE bre.id = rid; +END +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION oai.call_number_datestamp() + RETURNS TRIGGER AS $$ +BEGIN + IF TG_OP = 'DELETE' + THEN + PERFORM oai.datestamp(OLD.record); + RETURN OLD; + END IF; + + PERFORM oai.datestamp(NEW.record); + RETURN NEW; + +END +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION oai.copy_datestamp() + RETURNS TRIGGER AS $$ +BEGIN + IF TG_OP = 'DELETE' + THEN + PERFORM oai.datestamp((SELECT acn.record FROM asset.call_number as acn WHERE acn.id = OLD.call_number)); + RETURN OLD; + END IF; + + PERFORM oai.datestamp((SELECT acn.record FROM asset.call_number as acn WHERE acn.id = NEW.call_number)); + RETURN NEW; + +END +$$ LANGUAGE plpgsql; + +CREATE TRIGGER call_number_datestamp AFTER INSERT OR UPDATE OR DELETE ON asset.call_number FOR EACH ROW EXECUTE PROCEDURE oai.call_number_datestamp(); +CREATE TRIGGER copy_datestamp AFTER INSERT OR UPDATE OR DELETE ON asset.copy FOR EACH ROW EXECUTE PROCEDURE oai.copy_datestamp(); +``` + -- 2.43.2