1package Net::OAI::ListRecords;
2
3use strict;
4use warnings;
5use base qw( XML::SAX::Base Net::OAI::Base );
6use Carp qw( croak );
7use Net::OAI::Record;
8use Net::OAI::Record::Header;
9use File::Temp qw( tempfile );
10use Storable qw( store_fd fd_retrieve );
11use IO::File;
12
13=head1 NAME
14
15Net::OAI::ListRecords - Results of the ListRecords OAI-PMH verb.
16
17=head1 SYNOPSIS
18
19=head1 DESCRIPTION
20
21=head1 METHODS
22
23Like all responses to OAI verbs, ListRecords is based on L<Net::OAI::Base>
24and inherits its methods.
25
26
27=head2 new()
28
29You probably don't want to be using this method yourself, since
30Net::OAI::Harvester::listRecords() calls it for you.
31
32=cut
33
34sub new {
35    my ( $class, %opts ) = @_;
36
37    my $package;
38    if ( $package = $opts{ recordHandler } ) {
39        $opts{ metadataHandler } and croak( "you may pass either a recordHandler or a metadataHandler to getRecord()" );
40        delete $opts { metadataHandler };
41    } elsif ( $package = $opts{ metadataHandler } ) {
42	delete $opts{ recordHandler };
43    } else {
44        delete $opts{ recordHandler };
45	$package = $opts{ metadataHandler } = 'Net::OAI::Record::OAI_DC';
46    }
47    Net::OAI::Harvester::_verifyHandler( $package );
48
49    my $self = bless \%opts, ref( $class ) || $class;
50    my ( $fh, $tempfile ) = tempfile(UNLINK => 1);
51    binmode( $fh, ':utf8' );
52    $self->{ recordsFileHandle } = $fh;
53    $self->{ recordsFilename } = $tempfile;
54
55    ## so we can store code refs
56    $Storable::Deparse = 1;
57    $Storable::Eval = 1;
58
59    $self->{ _prefixmap } = {};
60    return( $self );
61}
62
63=head2 next()
64
65Returns the L<Net::OAI::Record> object for the next OAI record in the
66response, C<undef> if none remain. resumptionToken handling is performed
67automagically if the original request was listAllIdentifiers().
68
69=cut
70
71sub next {
72    my $self = shift;
73
74    ## if we haven't opened our object store do it now
75    if ( ! $self->{ recordsFileHandle } ) {
76	$self->{ recordsFileHandle } = IO::File->new( $self->{ recordsFilename } )
77	    or croak "unable to open temp file: ".$self->{ recordsFilename };
78	## we assume utf8 encoding (perhaps wrongly)
79        binmode( $self->{ recordsFileHandle }, ':utf8' );
80    }
81
82    ## no more data to read back from our object store then return undef
83    if ( $self->{ recordsFileHandle }->eof() ) {
84	$self->{ recordsFileHandle }->close() or croak "Could not close() ".$self->{ recordsFilename }
85                                                      .". File system full?";
86	return( $self->handleResumptionToken( 'listRecords' ) );
87    }
88
89    ## get an object back from the store, thaw and return it
90    my $record = fd_retrieve( $self->{ recordsFileHandle } );
91    return( $record );
92}
93
94=head2 metadataHandler()
95=head2 recordHandler()
96
97Returns the name of the package being used to represent the individual metadata
98records. If unspecified it defaults to L<Net::OAI::Record::OAI_DC> which
99should be ok.
100
101=cut
102
103sub metadataHandler {
104    my $self = shift;
105    return( $self->{ metadataHandler } );
106}
107
108sub recordHandler {
109    my $self = shift;
110    return( $self->{ recordHandler } );
111}
112
113## SAX Handlers
114
115sub start_prefix_mapping {
116  my ($self, $mapping) = @_;
117  if ( $self->get_handler() ) {
118      return $self->SUPER::start_prefix_mapping( $mapping )};
119  $self->{ _prefixmap }->{$mapping->{ Prefix }} = $mapping;
120}
121
122sub end_prefix_mapping {
123  my ($self, $mapping) = @_;
124  if ( $self->get_handler() ) {
125      return $self->SUPER::end_prefix_mapping( $mapping )};
126  delete $self->{ _prefixmap }->{$mapping->{ Prefix }};
127}
128
129sub start_element {
130    my ( $self, $element ) = @_;
131    return $self->SUPER::start_element( $element ) unless $element->{NamespaceURI} eq Net::OAI::Harvester::XMLNS_OAI;
132
133    ## if we are at the start of a new record then we need an empty
134    ## metadata object to fill up
135    if ( $element->{ LocalName } eq 'record' ) {
136	## we store existing downstream handler so we can replace
137	## it after we are done retrieving the metadata record
138	$self->{ OLD_Handler } = $self->get_handler();
139	my $header = $self->{ recordHandler }
140		   ? Net::OAI::Record::Header->new(
141			Handler => (ref($self->{ recordHandler }) ? $self->{ recordHandler } : $self->{ recordHandler }->new()),
142			fwdAll => 1,
143		     )
144		   : Net::OAI::Record::Header->new(
145			Handler => (ref($self->{ metadataHandler }) ? $self->{ metadataHandler } : $self->{ metadataHandler }->new()),
146                        ($Net::OAI::Harvester::OLDmetadataHandler ? (fwdAll => 1) : ()),
147		     );
148	$self->set_handler( $header );
149        foreach my $mapping ( values %{$self->{_prefixmap}} ) {
150            $self->SUPER::start_prefix_mapping($mapping)};
151    }
152    elsif ( $element->{ LocalName } eq 'ListRecords' ) {
153    }
154    return $self->SUPER::start_element( $element );
155}
156
157sub end_element {
158    my ( $self, $element ) = @_;
159
160    $self->SUPER::end_element( $element );
161    return unless $element->{NamespaceURI} eq Net::OAI::Harvester::XMLNS_OAI;
162
163    ## if we've got to the end of the record we need to stash
164    ## away the object in our object store on disk
165    if ( $element->{ LocalName } eq 'record' ) {
166
167	## we need to swap out the existing metadata handler and freeze
168	## it on disk
169	my $header = $self->get_handler();
170	my $data = $header->get_handler();
171	$header->set_handler( undef ); ## remove reference to $record
172
173	## set handler to what is was before we started processing
174	## the record
175	$self->set_handler( $self->{ OLD_Handler } );
176        my $record;
177        if ( $self->{ recordHandler } ) {
178	    $record = Net::OAI::Record->new(header => $header, recorddata => $data)
179        } else {
180	    $record = Net::OAI::Record->new(header => $header, metadata => $data)
181	};
182
183	## commit the object to disk
184        Net::OAI::Harvester::debug( "committing record to object store" );
185	store_fd( $record, $self->{ recordsFileHandle } );
186    }
187
188    ## otherwise if we got to the end of our list we can close
189    ## our object stash on disk
190    elsif ( $element->{ LocalName } eq 'ListRecords' ) {
191	$self->{ recordsFileHandle }->close();
192	$self->{ recordsFileHandle } = undef;
193    }
194
195}
196
197sub _fatal {
198    print STDERR "fatal: ", shift, "\n";
199    exit(1);
200}
201
2021;
203
204