and cleanup code to load correct format
[Biblio-Z3950.git] / server.pl
1 #!/usr/bin/perl
2
3 use warnings;
4 use strict;
5
6 use Net::Z3950::SimpleServer;
7 use Net::Z3950::OID;
8 use Data::Dumper;
9 use COBISS;
10 use Aleph;
11
12 my $databases = {
13         'COBISS' => 'COBISS',
14         'NSK01'  => 'Aleph',
15         'NSK10'  => 'Aleph',
16         'ZAG01'  => 'Aleph',
17 };
18
19 my $max_records = 3; # XXX configure this
20 my $max_result_sets = 10;
21
22 sub diag {
23         print "# ", @_, $/;
24 }
25
26 sub InitHandle {
27     my $this    = shift;
28     my $session = {};
29
30     $this->{HANDLE}   = $session;
31     $this->{IMP_NAME} = "Biblio Z39.50";
32     $this->{IMP_VER}  = "0.2";
33     $session->{SETS}  = {};
34 }
35
36 sub SearchHandle {
37     my $this    = shift;
38
39 diag "SearchHandle ",Dumper($this);
40
41     my $session = $this->{HANDLE};
42     my $rpn     = $this->{RPN};
43     my $query;
44
45         my $database = uc $this->{DATABASES}->[0];
46         my $module = $databases->{$database};
47         if ( ! defined $module ) {
48         $this->{ERR_CODE} = 108;
49                 warn $this->{ERR_STR} = "$database NOT FOUND in available databases: " . join(" ", keys %$databases);
50                 return;
51         }
52
53         my $from = $module->new( $database );
54
55 diag "using $module for $database ", Dumper( $from );
56
57         eval { $query = $rpn->{query}->render( $from->usemap ); };
58         warn "ERROR: $@" if $@;
59     if ( $@ && ref($@) ) {    ## Did someone/something report any errors?
60         $this->{ERR_CODE} = $@->{errcode};
61         $this->{ERR_STR}  = $@->{errstr};
62         return;
63     }
64
65 diag "search for $query";
66
67     my $setname  = $this->{SETNAME};
68     my $repl_set = $this->{REPL_SET};
69 diag "SETNAME $setname REPL_SET $repl_set";
70     my $hits;
71     unless ( $hits = $from->search( $query ) ) {
72         $this->{ERR_CODE} = 108;
73         return;
74     }
75 diag "got $hits hits";
76     my $rs   = {
77         lower => 1,
78         upper => $hits < $max_records ? $max_records : $hits,
79         hits  => $hits,
80                 from => $from,
81                 results => [ undef ], # we don't use 0 element
82                 database => $database,
83     };
84     my $sets = $session->{SETS};
85
86     if ( defined( $sets->{$setname} ) && !$repl_set ) {
87         $this->{ERR_CODE} = 21;
88         return;
89     }
90     if ( scalar keys %$sets >= $max_result_sets ) {
91         $this->{ERR_CODE} = 112;
92         $this->{ERR_STR}  = "Max number is $max_result_sets";
93         return;
94     }
95     $sets->{$setname} = $rs;
96     $this->{HITS} = $session->{HITS} = $hits;
97     $session->{QUERY} = $query;
98 }
99
100 sub FetchHandle {
101     my $this     = shift;
102     my $session  = $this->{HANDLE};
103     my $setname  = $this->{SETNAME};
104     my $req_form = $this->{REQ_FORM};
105     my $offset   = $this->{OFFSET};
106     my $sets     = $session->{SETS};
107     my $hits     = $session->{HITS};
108     my $rs;
109     my $record;
110
111     if ( !defined( $rs = $sets->{$setname} ) ) {
112         $this->{ERR_CODE} = 30;
113         return;
114     }
115     if ( $offset > $hits ) {
116         $this->{ERR_CODE} = 13;
117         return;
118     }
119
120     $this->{BASENAME} = "HtmlZ3950";
121
122         my $format =
123                 $req_form eq Net::Z3950::OID::xml()     ? 'xml' :
124                 $req_form eq Net::Z3950::OID::unimarc() ? 'unimarc' :
125                 $req_form eq Net::Z3950::OID::usmarc()  ? 'marc' : # XXX usmarc -> marc
126                 undef;
127
128         if ( ! $format ) {
129                 warn "ERROR: $req_form format not supported";
130         $this->{ERR_CODE} = 239; ## Unsupported record format
131         $this->{ERR_STR}  = $req_form;
132         return;
133         }
134
135         $this->{REP_FORM} = $req_form;
136
137         my $from = $rs->{from} || die "no from?";
138         # fetch records up to offset
139         while(  $#{ $rs->{results} } < $offset ) {
140                 push @{ $rs->{results} }, $from->next_marc;
141                 warn "# rs result ", $#{ $rs->{results} },"\n";
142         }
143
144         my $id = $rs->{results}->[$offset] || die "no id for record $offset in ",Dumper( $rs->{results} );
145
146         my $path = 'marc/' . $rs->{database} . "/$id.$format";
147         if ( ! -e $path ) {
148                 warn "ERROR: $path not found";
149                 ## Unsupported record format
150         $this->{ERR_CODE} = 239;
151         $this->{ERR_STR}  = $req_form;
152         return;
153     }
154
155         {
156                 open(my $in, '<', $path) || die "$path: $!";
157                 local $/ = undef;
158                 my $marc = <$in>;
159                 close($in);
160                 $this->{RECORD} = $marc;
161         }
162
163
164     if ( $offset == $hits ) {
165         $this->{LAST} = 1;
166     }
167     else {
168         $this->{LAST} = 0;
169     }
170 }
171
172 sub CloseHandle {
173     my $this = shift;
174 }
175
176 my $z = new Net::Z3950::SimpleServer(
177     INIT   => \&InitHandle,
178     SEARCH => \&SearchHandle,
179     FETCH  => \&FetchHandle,
180     CLOSE  => \&CloseHandle
181 );
182 $z->launch_server( $0, @ARGV );
183
184 package Net::Z3950::RPN::And;
185
186 sub render {
187     my $this = shift;
188     return $this->[0]->render() . ' AND ' . $this->[1]->render();
189 }
190
191 package Net::Z3950::RPN::Or;
192
193 sub render {
194     my $this = shift;
195     return $this->[0]->render() . ' OR ' . $this->[1]->render();
196 }
197
198 package Net::Z3950::RPN::AndNot;
199
200 sub render {
201     my $this = shift;
202     return $this->[0]->render() . ' AND NOT ' . $this->[1]->render();
203 }
204
205 package Net::Z3950::RPN::Term;
206
207 use Data::Dump qw(dump);
208 use COBISS;
209
210 sub render {
211         my ($this,$usemap) = @_;
212
213 warn "# render ", dump($this);
214 warn "# usemap ", dump($usemap);
215
216     my $attributes = {};
217     my $prefix     = "";
218     foreach my $attr ( @{ $this->{attributes} } ) {
219         my $type  = $attr->{attributeType};
220         my $value = $attr->{attributeValue};
221         $attributes->{$type} = $value;
222     }
223     if ( defined( my $use = $attributes->{1} ) ) {
224         if ( defined( my $field = $usemap->{$use} ) ) {
225             $prefix = $field;
226         }
227         else {
228             die { errcode => 114, errstr => $use }; ## Unsupported use attribute
229         }
230     }
231     if ( defined( my $rel = $attributes->{2} ) )
232     {    ## No relation attributes supported
233         if ( $rel != 3 ) {
234             die { errcode => 117, errstr => $rel };
235         }
236     }
237     if ( defined( my $pos = $attributes->{3} ) )
238     {    ## No position attributes either
239         if ( $pos != 3 ) {
240             die { errcode => 119, errstr => $pos };
241         }
242     }
243     if ( defined( my $struc = $attributes->{4} ) ) {    ## No structure
244         if ( ( $struc != 1 ) && ( $struc != 2 ) ) {
245             die { errcode => 118, errstr => $struc };
246         }
247     }
248     if ( defined( $attributes->{5} ) ) {                ## No truncation
249         die { errcode => 113, errstr => 5 };
250     }
251     my $comp = $attributes->{6};
252     if ($prefix) {
253         if ( defined($comp) && ( $comp >= 2 ) ) {
254             $prefix = "all$prefix= ";
255         }
256         else {
257             $prefix = "$prefix=";
258         }
259     }
260
261     my $q = $prefix . $this->{term};
262         print "# q: $q\n";
263         return $q;
264 }
265