LP#1729620: Polish up sets and visibility tests
authorMike Rylander <mrylander@gmail.com>
Wed, 26 Jan 2022 21:36:27 +0000 (16:36 -0500)
committerJane Sandberg <sandbergja@gmail.com>
Mon, 28 Mar 2022 02:59:20 +0000 (19:59 -0700)
This commit improves the logic needed to control record inclusion in
OAI Sets.  Sets for bib records are currently based on copy visibility,
Located URI visibility, or bib source.  Generally useful combinations
are generated for item, Located URI, and transcendant [sic] bib sources.

Sets for authority records are based on the browse axis of the heading
for the record.

All generated Sets for bib records currently test for effective search
visibilty on the three described components.

In order to retrieve item data from a specific branch of the org tree,
a harvester must use a Set that contains COPIES:. The hierarchial
shortnames of the org units follow the colon, so in a freshly installed
system a Set with the setSpec of COPIES:CONS:SYS2:BR3 will retrieve all
bib records that have visible copies at BR3, and include only the item
data for that org unit.

To retrieve records with Located URIs, which behave in the heirarchical
reverse of copies by limiting display to those org units "inside" the
branch of the tree where the licensing "lives", a harvester should
supply a setSpec with an org unit shortname that would display the
Located URI. For example: LURIS:CONS:SYS2:BR3

Note: the LURI: org unit is also used to restrict any item data that may
be available for LURI-filtered records.

To retrive records are in a transcendant [sic] bib source, a harvester
can used a setSpec starting with SOURCES: followed by the source name.

To harvest all records, including deleted records, simply omit the
setSpec from the request.  All item data will be included.

Signed-off-by: Mike Rylander <mrylander@gmail.com>
Signed-off-by: Jane Sandberg <sandbergja@gmail.com>

Open-ILS/examples/fm_IDL.xml
Open-ILS/examples/opensrf.xml.example
Open-ILS/src/perlmods/lib/OpenILS/Application/SuperCat/OAI.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat/OAI.pm
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.oai_views.sql
Open-ILS/xsl/OAI2_MARC21slim.xsl [deleted file]
Open-ILS/xsl/OAI2_OAIDC.xsl [deleted file]

index 878ea54..e3bdba8 100644 (file)
@@ -75,21 +75,21 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
        <!-- Virtual classes -->
 
-       <class id="oai_biblio" controller="open-ils.cstore" oils_obj:fieldmapper="oai::biblio"
+       <class id="oaib" controller="open-ils.cstore" oils_obj:fieldmapper="oai::biblio"
                   oils_persist:readonly="true" reporter:core="false" reporter:label="OAI2 record list"
                   oils_persist:tablename="oai.biblio">
-               <fields>
-                       <field reporter:label="Record ID\OAI identifier postfix" name="rec_id" reporter:datatype="number"/>
+               <fields oils_persist:primary="rec_id">
+                       <field reporter:label="Record ID\OAI identifier postfix" name="rec_id" reporter:datatype="id"/>
                        <field reporter:label="Last edit date\OAI datestamp" name="datestamp" reporter:datatype="timestamp"/>
                        <field reporter:label="Is Deleted?" name="deleted" reporter:datatype="bool"/>
                        <field reporter:label="Setspec" name="set_spec" oils_persist:virtual="true"/>
                </fields>
        </class>
-       <class id="oai_authority" controller="open-ils.cstore" oils_obj:fieldmapper="oai::authority"
+       <class id="oaia" controller="open-ils.cstore" oils_obj:fieldmapper="oai::authority"
                   oils_persist:readonly="true" reporter:core="false" reporter:label="OAI2 record list"
                   oils_persist:tablename="oai.authority">
-               <fields>
-                       <field reporter:label="Record ID\OAI identifier postfix" name="rec_id" reporter:datatype="number"/>
+               <fields oils_persist:primary="rec_id">
+                       <field reporter:label="Record ID\OAI identifier postfix" name="rec_id" reporter:datatype="id"/>
                        <field reporter:label="Last edit date\OAI datestamp" name="datestamp" reporter:datatype="timestamp"/>
                        <field reporter:label="Is Deleted?" name="deleted" reporter:datatype="bool"/>
                        <field reporter:label="Setspec" name="set_spec" oils_persist:virtual="true"/>
index ca1ff3e..05d945f 100644 (file)
@@ -834,12 +834,11 @@ vim:et:ts=4:sw=4:
                             <oai_dc>
                                 <namespace_uri>http://www.openarchives.org/OAI/2.0/oai_dc/</namespace_uri>
                                 <schema_location>http://www.openarchives.org/OAI/2.0/oai_dc.xsd</schema_location>
-                                <xslt>OAI2_OAIDC.xsl</xslt>
+                                <xslt>MARC21slim2OAIDC.xsl</xslt>
                             </oai_dc>
                             <marcxml>
                                 <namespace_uri>http://www.loc.gov/MARC21/slim</namespace_uri>
                                 <schema_location>http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd</schema_location>
-                                <xslt>OAI2_MARC21slim.xsl</xslt>
                             </marcxml>
                         </metadataformat> -->
 
index 8910735..c2a83b6 100644 (file)
@@ -74,7 +74,7 @@ sub child_init {
             OpenSRF::Utils::SettingsClient
                 ->new
                 ->config_value( dirs => 'xsl' ).
-            "/OAI2_OAIDC.xsl"
+            "/MARC21slim2OAIDC.xsl"
         );
         # and stash a transformer
         $record_xslt{oai_dc}{xslt} = $_xslt->parse_stylesheet( $xslt );
@@ -82,28 +82,18 @@ sub child_init {
         $record_xslt{oai_dc}{schema_location} = 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd';
     }
 
-    # If not defined, use the natural marcxml metadata setting
-    unless ( exists $record_xslt{marcxml}) {
-        $logger->info('Loading default marcxml schema') ;
-        my $xslt = $_parser->parse_file(
-            OpenSRF::Utils::SettingsClient
-                ->new
-                ->config_value( dirs => 'xsl' ).
-            "/OAI2_MARC21slim.xsl"
-        );
-        # and stash a transformer
-        $record_xslt{marcxml}{xslt} = $_xslt->parse_stylesheet( $xslt );
-        $record_xslt{marcxml}{namespace_uri} = 'http://www.loc.gov/MARC21/slim';
-        $record_xslt{marcxml}{docs} = 'http://www.loc.gov/MARC21/slim';
-        $record_xslt{marcxml}{schema_location} = 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd';
-    }
+    # Store info about the natural marcxml metadata setting. We don't actually use this to drive XSLT, but we can report support.
+    $logger->info('Loading default marcxml schema') ;
+    $record_xslt{marcxml}{namespace_uri} = 'http://www.loc.gov/MARC21/slim';
+    $record_xslt{marcxml}{docs} = 'http://www.loc.gov/MARC21/slim';
+    $record_xslt{marcxml}{schema_location} = 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd';
 
     # Load the mapping of 852 holdings.
     my $copies = OpenSRF::Utils::SettingsClient->new->config_value(apps => 'open-ils.supercat')->{'app_settings'}->{'oai'}->{'copies'} ;
     if ( $copies ) {
         foreach my $subfield_code (keys %$copies) {
             my $value = $copies->{$subfield_code};
-            $logger->info('Set 852 map ' . $subfield_code . '=' . $value );
+            $logger->debug('Set 852 map ' . $subfield_code . '=' . $value );
             $copies{$subfield_code} = $value;
         }
     } else { # if not defined, fall back on these defaults.
@@ -164,6 +154,19 @@ sub oai_biblio_retrieve {
     my $client = shift;
     my $rec_id = shift;
     my $metadataPrefix = shift;
+    my $set = shift;
+    my $copy_org;
+    my %copy_org_filter;
+
+    (undef, $copy_org) = _set_spec_to_query('biblio',$set) if ($set);
+
+    my $flesh = {};
+    if ($copy_org) {
+        $U->walk_org_tree($copy_org, sub {
+            my $c = shift;
+            $copy_org_filter{$c->id} = 1;
+        });
+    }
 
     #  holdings hold an array of call numbers, which hold an array of copies
     #  holdings => [ label: { library, [ copies: { barcode, location, status, circ_lib } ] } ]
@@ -177,13 +180,15 @@ sub oai_biblio_retrieve {
         $rec_id,
         { flesh     => 5,
           flesh_fields  => {
-                    bre => [qw/marc edit_date call_numbers/],
-                    acn => [qw/edit_date copies owning_lib prefix suffix/],
-                    acp => [qw/edit_date location status circ_lib parts/],
+                    bre => [qw/call_numbers/],
+                    acn => [qw/copies owning_lib prefix suffix uris/],
+                    acp => [qw/location status circ_lib parts/],
                 }
         }
     )->gather(1);
 
+    $tree->call_numbers([]) if (!$tree->call_numbers);
+
     # Create a MARC::Record object with the marc.
     my $marc = MARC::Record->new_from_xml( $tree->marc, 'UTF8', 'XML');
 
@@ -211,21 +216,21 @@ sub oai_biblio_retrieve {
     for my $cn (@{$tree->call_numbers}) {
 
         next unless ( $cn->deleted eq 'f' || !$cn->deleted );
-        my $_visible = 0;
-        for my $c (@{$cn->copies}) {
-            $_visible = _cp_is_visible($cn, $c);
-            last if ( $_visible );
+
+        my @visible_copies = @{$cn->copies};
+        if ($copy_org) {
+            @visible_copies = grep { $copy_org_filter{$_->circ_lib->id} } @visible_copies;
         }
-        next unless $_visible;
+        @visible_copies = grep { _cp_is_visible($cn, $_) } @visible_copies;
+        next unless @visible_copies;
 
         my $cn_label = $cn->label;
         $holdings{$cn_label}{'owning_lib'} = $cn->owning_lib->shortname;
 
         $edit_date =  most_recent_date( $cn->edit_date, $edit_date );
 
-        for my $cp (@{$cn->copies}) {
+        for my $cp (@visible_copies) {
 
-            next unless _cp_is_visible($cn, $cp);
             $edit_date = most_recent_date( $cp->edit_date, $edit_date );
 
             # find the corresponding serial.
@@ -250,7 +255,7 @@ sub oai_biblio_retrieve {
                 barcode    => $cp->barcode,
                 status     => $cp->status->name,
                 location   => $cp->location->name,
-                circlib    => $circlib,
+                circlib    => $cp->circ_lib->shortname,
                 ser        => $ser
             };
         }
@@ -297,6 +302,9 @@ sub oai_biblio_retrieve {
 
     }
 
+    $XML::LibXML::skipXMLDeclaration = 1;
+    return $marc->as_xml_record() if ($metadataPrefix eq 'marcxml');
+
     my $xslt = $record_xslt{$metadataPrefix}{xslt} ;
     my $xml = $xslt->transform( $_parser->parse_string( $marc->as_xml_record()) );
     return $xslt->output_as_chars( $xml ) ;
@@ -392,6 +400,9 @@ sub oai_authority_retrieve {
         );
     }
 
+    local $XML::LibXML::skipXMLDeclaration = 1;
+    return $marc->as_xml_record() if ($metadataPrefix eq 'marcxml');
+
     my $xslt = $record_xslt{$metadataPrefix}{xslt} ;
     my $xml = $record_xslt{$metadataPrefix}{xslt}->transform(
        $_parser->parse_string( $marc->as_xml_record())
@@ -442,7 +453,8 @@ sub oai_list_retrieve {
     my $max_count       = shift;
     my $deleted_record  = shift || 'yes';
 
-    my $query = $set ? _set_spec_to_query($set) : {};
+    my ($query) = _set_spec_to_query($record_class,$set) if ($set);
+
     $query->{'rec_id'}    = ($max_count eq 1) ? $rec_id : {'>=' => $rec_id} ;
     $query->{'deleted'}   = 'f'                      unless ( $deleted_record eq 'yes' );
     $query->{'datestamp'} = {'>=', $from}            if ( $from && !$until ) ;
@@ -512,22 +524,67 @@ __PACKAGE__->register_method(
 );
 
 sub _set_spec_to_query {
-    my $self            = shift;
-    my $set_spec        = shift;
-
-    if (index($set_spec, 'ORG_UNIT:') == 0) {
-        my $shortname = (split ':', $set_spec)[-1];
-        my $org_unit = OpenSRF::AppSession
-            ->create('open-ils.actor')
-            ->request('open-ils.actor.org_unit.retrieve_by_shortname', $shortname)
-            ->gather(1);
-        if ($org_unit && ref($org_unit) ne 'HASH') {
-            return {
-                'id' => {'=' => ['oai.bib_is_visible_at_org', 'id', $org_unit->id]}
-            };
+    my $type = shift;
+    my $set_spec = shift;
+    my $query_part = {};
+    my $copy_org;
+
+    if ($type eq 'biblio') {
+        if ($set_spec =~ /COPIES:([^!]+)/) {
+            my $org_list = $1;
+            my $shortname = (split ':', $org_list)[-1];
+            my $org_unit = $U->find_org_by_shortname($U->get_org_tree, $shortname);
+            if ($org_unit) {
+                $copy_org = $org_unit;
+                $$query_part{'-or'} //= [];
+                push @{$$query_part{'-or'}}, {rec_id => {'=' => {
+                    transform => 'oai.bib_is_visible_at_org_by_copy',
+                    params    => [$org_unit->id],
+                    value     => ['bool','1']
+                }}};
+            }
+        }
+
+        if ($set_spec =~ /LURIS:([^!]+)/) {
+            my $org_list = $1;
+            my $shortname = (split ':', $org_list)[-1];
+            my $org_unit = $U->find_org_by_shortname($U->get_org_tree, $shortname);
+            if ($org_unit) {
+                $copy_org = $org_unit;
+                $$query_part{'-or'} //= [];
+                push @{$$query_part{'-or'}}, {rec_id => {'=' => {
+                    transform => 'oai.bib_is_visible_at_org_by_luri',
+                    params    => [$org_unit->id],
+                    value     => ['bool','1']
+                }}};
+            }
+        }
+
+        if ($set_spec =~ /SOURCES:([^!]+)/) {
+            my $list = $1;
+            my @sources = split ':', $list;
+            for my $source (@sources) {
+                $$query_part{'-or'} //= [];
+                push @{$$query_part{'-or'}}, {rec_id => {'=' => {
+                    transform => 'oai.bib_is_visible_by_source',
+                    params    => [$source],
+                    value     => ['bool','1']
+                }}};
+            }
+        }
+    } elsif ($type eq 'authority') {
+        my @axes = split ':', $set_spec;
+        for my $axis (@axes) {
+            $$query_part{'-or'} //= [];
+            push @{$$query_part{'-or'}}, {rec_id => {'=' => {
+                transform => 'oai.auth_is_visible_by_axis',
+                params    => [$axis],
+                value     => ['bool','1']
+            }}};
         }
     }
-    return {};
+
+    return ($query_part, $copy_org);
 }
 
 
index a348ad1..e05d436 100644 (file)
@@ -32,9 +32,14 @@ use MARC::Record;
 use MIME::Base64;
 use OpenSRF::EX qw(:try);
 use OpenSRF::Utils::Logger qw/$logger/;
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Utils::CStoreEditor;
+use OpenILS::Application::AppUtils;
 use XML::LibXML;
 use XML::LibXSLT;
 
+my $U = 'OpenILS::Application::AppUtils';
+
 my (
     $bootstrap,
     $base_url,
@@ -50,7 +55,6 @@ my (
     $sample_identifier,
     $oai_metadataformats,
     $oai_sets,
-    $oai,
     $parser,
     $xslt
 );
@@ -69,8 +73,8 @@ sub child_init {
 
     my $idl = OpenSRF::Utils::SettingsClient->new->config_value('IDL');
     Fieldmapper->import(IDL => $idl);
+    OpenILS::Utils::CStoreEditor->init; # just in case
 
-    $oai = OpenSRF::AppSession->create('open-ils.supercat');
     $parser = new XML::LibXML;
     $xslt = new XML::LibXSLT;
 
@@ -88,10 +92,14 @@ sub child_init {
     $delimiter = $app_settings->{'delimiter'} || ':';
     $sample_identifier = $app_settings->{'sample_identifier'} || $scheme . $delimiter . $repository_identifier . $delimiter . '12345' ;
 
-    _load_oaisets_authority();
+    $logger->info('Default OAI repo settings in place, loading sets...');
+
     _load_oaisets_biblio();
+    _load_oaisets_authority();
     _load_oai_metadataformats();
 
+    $logger->info('... sets loaded.');
+
     return Apache2::Const::OK;
 }
 
@@ -168,7 +176,7 @@ sub handler {
                 $response = listSets( $record_class, $requestURL );
             }
             elsif ( $verb eq 'GetRecord' ) {
-                $response = getRecord( $record_class, $requestURL, $identifier, $metadataPrefix);
+                $response = getRecord( $record_class, $requestURL, $identifier, $metadataPrefix, $set);
             }
             elsif ( $verb eq 'ListIdentifiers' ) {
                 $response = listIdentifiers( $record_class, $requestURL, $from, $until, $set, $metadataPrefix, $offset);
@@ -264,7 +272,7 @@ sub listSets {
 
 sub getRecord {
 
-    my ($record_class, $requestURL, $identifier, $metadataPrefix ) = @_;
+    my ($record_class, $requestURL, $identifier, $metadataPrefix, $set ) = @_;
 
     my $response ;
     my @errors;
@@ -275,11 +283,10 @@ sub getRecord {
         my $rec_id = $1 ;
 
         # Do we have a record ?
-        my $record = $oai->request('open-ils.supercat.oai.list.retrieve', $record_class, $rec_id, undef, undef, undef, 1, $deleted_record)->gather(1) ;
+        my $record = $U->simplereq('open-ils.supercat','open-ils.supercat.oai.list.retrieve', $record_class, $rec_id, undef, undef, undef, 1, $deleted_record);
         if (@$record) {
             $response = HTTP::OAI::GetRecord->new();
-            my $o = "Fieldmapper::oai::$record_class"->new(@$record[0]);
-            $response->record(_record($record_class, $o, $metadataPrefix));
+            $response->record(_record($record_class, $$record[0], $metadataPrefix, $set));
         } else {
             push @errors, new HTTP::OAI::Error(code=>'idDoesNotExist', message=>'The value of the identifier argument is unknown or illegal in this repository.') ;
         }
@@ -302,18 +309,17 @@ sub listIdentifiers {
     my ($record_class, $requestURL, $from, $until, $set, $metadataPrefix, $offset ) = @_;
     my $response;
 
-    my $r = $oai->request('open-ils.supercat.oai.list.retrieve', $record_class, $offset, $from, $until, $set, $max_count, $deleted_record)->gather(1) ;
+    my $r = $U->simplereq('open-ils.supercat','open-ils.supercat.oai.list.retrieve', $record_class, $offset, $from, $until, $oai_sets->{$set}->{setSpec}, $max_count, $deleted_record);
     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->rec_id ), '' ) ) ;
+                my $token = new HTTP::OAI::ResumptionToken( resumptionToken => encode_base64(join( '$', $metadataPrefix, $from, $until, $oai_sets->{$set}->{setSpec}, $record->rec_id ), '' ) ) ;
                 $token->cursor($offset);
                 $response->resumptionToken($token) ;
             } else {
-                $response->identifier( _header($record_class, $o)) ;
+                $response->identifier( _header($record_class, $record)) ;
             }
         }
     } else {
@@ -331,18 +337,17 @@ sub listRecords {
     my ($record_class, $requestURL, $from, $until, $set, $metadataPrefix, $offset ) = @_;
     my $response;
 
-    my $r = $oai->request('open-ils.supercat.oai.list.retrieve', $record_class, $offset, $from, $until, $set, $max_count, $deleted_record)->gather(1) ;
+    my $r = $U->simplereq('open-ils.supercat','open-ils.supercat.oai.list.retrieve', $record_class, $offset, $from, $until, $oai_sets->{$set}->{setSpec}, $max_count, $deleted_record);
     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->rec_id ), '' ) ) ;
+                my $token = new HTTP::OAI::ResumptionToken( resumptionToken => encode_base64(join( '$', $metadataPrefix, $from, $until, $oai_sets->{$set}->{setSpec}, $record->rec_id ), '' ) ) ;
                 $token->cursor($offset);
                 $response->resumptionToken($token) ;
             } else {
-                $response->record(_record($record_class, $o, $metadataPrefix));
+                $response->record(_record($record_class, $record, $metadataPrefix, $set));
             }
         }
     } else {
@@ -379,14 +384,15 @@ sub _header {
 
 sub _record {
 
-    my ($record_class, $o, $metadataPrefix ) = @_;
+    my ($record_class, $o, $metadataPrefix, $set ) = @_;
 
     my $record = HTTP::OAI::Record->new();
     $record->header( _header($record_class, $o) );
 
     if ( $o->deleted eq 'f' ) {
         my $md = new HTTP::OAI::Metadata() ;
-        my $xml = $oai->request('open-ils.supercat.oai.' . $record_class . '.retrieve', $o->rec_id, $metadataPrefix)->gather(1) ;
+        my $xml = $U->simplereq('open-ils.supercat','open-ils.supercat.oai.' . $record_class . '.retrieve', $o->rec_id, $metadataPrefix, $oai_sets->{$set}->{setSpec});
+        $xml =~ s/^<\?xml[^?]+?\?>//;
         $md->dom( $parser->parse_string('<metadata>' . $xml . '</metadata>') ); # Not sure why I need to add the metadata element,
         $record->metadata( $md );                                               # because I expect ->metadata() would provide the wrapper for it.
     }
@@ -400,12 +406,9 @@ sub _record {
 # oai_sets = {id\setSpec => {id, setSpec, setName, record_class = 'authority' }}
 sub _load_oaisets_authority {
 
-    my $ses = OpenSRF::AppSession->create('open-ils.cstore');
-    my $r = $ses->request('open-ils.cstore.direct.authority.browse_axis.search.atomic',
-        {code => {'!=' => undef } } )->gather(1);
+    my $axes = $U->simplereq('open-ils.cstore','open-ils.cstore.direct.authority.browse_axis.search.atomic', {code => {'!=' => undef } } );
 
-    for my $record (@$r) {
-        my $o = Fieldmapper::authority::browse_axis->new($record) ;
+    for my $o (@$axes) {
         $oai_sets->{$o->code} = {
            id => $o->code,
            setSpec => $o->code,
@@ -419,35 +422,62 @@ sub _load_oaisets_authority {
 # _load_oaisets_biblio
 # Populate the $oai_sets hash with the sets for bibliographic records. Those are org_type records
 # oai_sets = {id\setSpec => {id, setSpec, setName, record_class = 'biblio' }}
+my $org_tree;
+my $bib_sources;
 sub _load_oaisets_biblio {
 
     my $node = shift;
-    my $types = shift;
     my $parent = shift;
 
-    unless ( $node ) {
-        my $ses = OpenSRF::AppSession->create('open-ils.actor');
-        $node = $ses->request('open-ils.actor.org_tree.retrieve')->gather(1);
-        my $aout = $ses->request('open-ils.actor.org_types.retrieve')->gather(1);
-        $ses->disconnect;
-        return unless ($node) ;
-
-        my @_types;
-        foreach my $type (@$aout) {
-            $_types[int($type->id)] = $type;
-        }
-        $types = \@_types;
+    if (!$node) {
+        $org_tree ||= $U->get_org_tree;
+        $bib_sources ||= $U->simplereq('open-ils.cat','open-ils.cat.bib_sources.retrieve.all');
+        
+        $node = $org_tree;
     }
 
     return unless ($node->opac_visible =~ /^[y1t]+/i);
 
+
     my $ou_hierarchy_string = ($parent) ? $parent . ':' . $node->shortname : $node->shortname ;
-    my $spec = 'ORG_UNIT:'.$ou_hierarchy_string;
-    $oai_sets->{$spec} = {id => $node->id, record_class => 'biblio' };
-    $oai_sets->{$node->id} = {setSpec => $spec, setName => $node->name, record_class => 'biblio' };
+    $logger->info('Registering setSpec list for ' . $ou_hierarchy_string);
+
+    my $cspec = 'COPIES:'.$ou_hierarchy_string;
+    $oai_sets->{$cspec} = {id => 'C'.$node->id, record_class => 'biblio' };
+    $oai_sets->{'C'.$node->id} = {setSpec => $cspec, setName => $node->name . ' / by copies', record_class => 'biblio' };
+
+    my $lspec = 'LURIS:'.$ou_hierarchy_string;
+    $oai_sets->{$lspec} = {id => 'L'.$node->id, record_class => 'biblio' };
+    $oai_sets->{'L'.$node->id} = {setSpec => $lspec, setName => $node->name . ' / by LURIs', record_class => 'biblio' };
+
+    my $clspec = $cspec . '!' . $lspec;
+    $oai_sets->{$clspec} = {id => 'CL'.$node->id, record_class => 'biblio' };
+    $oai_sets->{'CL'.$node->id} = {setSpec => $clspec, setName => $node->name . ' / by copies and LURIs', record_class => 'biblio' };
+
+
+    my $source_string;
+    for my $s (@$bib_sources) {
+
+        my $sspec = 'SOURCES:'.$s->source;
+        $oai_sets->{$sspec} = {id => 'S'.$s->id, record_class => 'biblio' };
+        $oai_sets->{'S'.$s->id} = {setSpec => $sspec, setName => $s->source . ' / by source', record_class => 'biblio' };
+
+        my $csspec = $cspec . '!' . $sspec;
+        $oai_sets->{$csspec} = {id => $s->id.'CS'.$node->id, record_class => 'biblio' };
+        $oai_sets->{$s->id.'CS'.$node->id} = {setSpec => $csspec, setName => $node->name . ' / by copies and source', record_class => 'biblio' };
+
+        my $lsspec = $lspec . '!' . $sspec;
+        $oai_sets->{$lsspec} = {id => $s->id.'LS'.$node->id, record_class => 'biblio' };
+        $oai_sets->{$s->id.'LS'.$node->id} = {setSpec => $lsspec, setName => $node->name . ' / by LURIs and source', record_class => 'biblio' };
+
+        my $clsspec = $clspec . '!' . $sspec;
+        $oai_sets->{$clsspec} = {id => $s->id.'CLS'.$node->id, record_class => 'biblio' };
+        $oai_sets->{$s->id.'CLS'.$node->id} = {setSpec => $clsspec, setName => $node->name . ' / by copies, LURIs, and source', record_class => 'biblio' };
+
+    }
 
     my $kids = $node->children;
-    _load_oaisets_biblio($_, $types, $ou_hierarchy_string) for (@$kids);
+    _load_oaisets_biblio($_, $ou_hierarchy_string) for (@$kids);
 }
 
 
@@ -456,7 +486,7 @@ sub _load_oaisets_biblio {
 # oai_metadataformats = { metadataPrefix => { schema, metadataNamespace } }
 sub _load_oai_metadataformats {
 
-    my $list = $oai->request('open-ils.supercat.oai.record.formats')->gather(1);
+    my $list = $U->simplereq('open-ils.supercat','open-ils.supercat.oai.record.formats');
     for my $record_browse_format ( @$list ) {
         my %h = %$record_browse_format ;
         my $metadataPrefix = (keys %h)[0] ;
index 233f490..2b9d007 100644 (file)
@@ -9,7 +9,7 @@ CREATE SCHEMA oai;
 CREATE VIEW oai.biblio AS
   SELECT
     bre.id                             AS rec_id,
-    bre.edit_date                      AS datestamp,
+    bre.edit_date AT TIME ZONE 'UTC'   AS datestamp,
     bre.deleted                        AS deleted
   FROM
     biblio.record_entry bre
@@ -19,17 +19,30 @@ CREATE VIEW oai.biblio AS
 -- 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
+    are.id                           AS rec_id,
+    are.edit_date AT TIME ZONE 'UTC' AS datestamp,
+    are.deleted                      AS deleted
   FROM
     authority.record_entry AS are
   ORDER BY
     are.id;
 
-CREATE OR REPLACE function oai.bib_is_visible_at_org(bib BIGINT, org INT) RETURNS BOOL AS $F$
-WITH orgs AS (SELECT array_accum(id) id FROM actor.org_unit_descendants(org))
-SELECT EXISTS (SELECT 1 FROM asset.copy_vis_attr_cache, orgs WHERE vis_attr_vector @@ search.calculate_visibility_attribute_test('circ_lib', orgs.id)::query_int AND bib=record)
+CREATE OR REPLACE function oai.bib_is_visible_at_org_by_copy(bib BIGINT, org INT) RETURNS BOOL AS $F$
+WITH corgs AS (SELECT array_accum(id) AS list FROM actor.org_unit_descendants(org))
+  SELECT EXISTS (SELECT 1 FROM asset.copy_vis_attr_cache, corgs WHERE vis_attr_vector @@ search.calculate_visibility_attribute_test('circ_lib', corgs.list)::query_int AND bib=record)
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE function oai.bib_is_visible_at_org_by_luri(bib BIGINT, org INT) RETURNS BOOL AS $F$
+WITH lorgs AS(SELECT array_accum(id) AS list FROM actor.org_unit_ancestors(org))
+  SELECT EXISTS (SELECT 1 FROM biblio.record_entry, lorgs WHERE vis_attr_vector @@ search.calculate_visibility_attribute_test('luri_org', lorgs.list)::query_int AND bib=id)
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE function oai.bib_is_visible_by_source(bib BIGINT, src TEXT) RETURNS BOOL AS $F$
+  SELECT EXISTS (SELECT 1 FROM biblio.record_entry b JOIN config.bib_source s ON (b.source = s.id) WHERE transcendant AND s.source = src AND bib=b.id)
+$F$ LANGUAGE SQL STABLE;
+
+CREATE OR REPLACE function oai.auth_is_visible_by_axis(auth BIGINT, ax TEXT) RETURNS BOOL AS $F$
+  SELECT EXISTS (SELECT 1 FROM authority.browse_axis_authority_field_map m JOIN authority.simple_heading r on (r.atag = m.field AND r.record = auth AND m.axis = ax))
 $F$ LANGUAGE SQL STABLE;
 
 COMMIT;
diff --git a/Open-ILS/xsl/OAI2_MARC21slim.xsl b/Open-ILS/xsl/OAI2_MARC21slim.xsl
deleted file mode 100644 (file)
index 3bf4226..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-<xsl:stylesheet version="1.0"
-                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
-                xmlns:marc="http://www.loc.gov/MARC21/slim">
-    <xsl:output omit-xml-declaration="yes"/>
-
-    <xsl:template match="node()|@*">
-        <xsl:copy>
-            <xsl:apply-templates select="node()|@*"/>
-        </xsl:copy>
-    </xsl:template>
-
-    <xsl:template match="*">
-        <xsl:element name="marc:{name()}" namespace="http://www.loc.gov/MARC21/slim">
-            <xsl:copy-of select="namespace::*"/>
-            <xsl:apply-templates select="node()|@*"/>
-        </xsl:element>
-    </xsl:template>
-</xsl:stylesheet>
\ No newline at end of file
diff --git a/Open-ILS/xsl/OAI2_OAIDC.xsl b/Open-ILS/xsl/OAI2_OAIDC.xsl
deleted file mode 100644 (file)
index 4434360..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<xsl:stylesheet version="1.0" xmlns:oai_dc="http://www.openarchives.org/OAI/2.0/oai_dc/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
-       <xsl:import href="MARC21slim2OAIDC.xsl"/>
-       <xsl:output omit-xml-declaration="yes"/>
-
-    <xsl:template match="/">
-                       <oai_dc:dc xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/oai_dc/ http://www.openarchives.org/OAI/2.0/oai_dc.xsd"
-                       xmlns:dc="http://purl.org/dc/elements/1.1/">
-                               <xsl:apply-templates/>
-                       </oai_dc:dc>
-       </xsl:template>
-
-</xsl:stylesheet>
\ No newline at end of file