Added new, OO-specific, error-codes to the ZOOM::Error class
[ZOOM-Perl-moved-to-github.git] / lib / ZOOM.pm
1 # $Id: ZOOM.pm,v 1.10 2005-10-31 15:10:49 mike Exp $
2
3 use strict;
4 use warnings;
5 use Net::Z3950::ZOOM;
6
7
8 package ZOOM;
9
10
11 # Member naming convention: hash-element names which begin with an
12 # underscore represent underlying ZOOM-C object descriptors; those
13 # which lack them represent Perl's ZOOM objects.  (The same convention
14 # is used in naming local variables where appropriate.)
15 #
16 # So, for example, the ZOOM::Connection class has an {_conn} element,
17 # which is a pointer to the ZOOM-C Connection object; but the
18 # ZOOM::ResultSet class has a {conn} element, which is a reference to
19 # the Perl-level Connection object by which it was created.  (It may
20 # be that we find we have no need for these references, but for now
21 # they are retained.)
22 #
23 # To get at the underlying ZOOM-C connection object of a result-set
24 # (if you ever needed to do such a thing, which you probably don't)
25 # you'd use $rs->{conn}->_conn().
26
27 # ----------------------------------------------------------------------------
28
29 # The "Error" package contains constants returned as error-codes.
30 package ZOOM::Error;
31 sub NONE { Net::Z3950::ZOOM::ERROR_NONE }
32 sub CONNECT { Net::Z3950::ZOOM::ERROR_CONNECT }
33 sub MEMORY { Net::Z3950::ZOOM::ERROR_MEMORY }
34 sub ENCODE { Net::Z3950::ZOOM::ERROR_ENCODE }
35 sub DECODE { Net::Z3950::ZOOM::ERROR_DECODE }
36 sub CONNECTION_LOST { Net::Z3950::ZOOM::ERROR_CONNECTION_LOST }
37 sub INIT { Net::Z3950::ZOOM::ERROR_INIT }
38 sub INTERNAL { Net::Z3950::ZOOM::ERROR_INTERNAL }
39 sub TIMEOUT { Net::Z3950::ZOOM::ERROR_TIMEOUT }
40 sub UNSUPPORTED_PROTOCOL { Net::Z3950::ZOOM::ERROR_UNSUPPORTED_PROTOCOL }
41 sub UNSUPPORTED_QUERY { Net::Z3950::ZOOM::ERROR_UNSUPPORTED_QUERY }
42 sub INVALID_QUERY { Net::Z3950::ZOOM::ERROR_INVALID_QUERY }
43 # The following are added specifically for this OO interface
44 sub CREATE_QUERY { 20001 }
45 sub QUERY_CQL { 20002 }
46 sub QUERY_PQF { 20003 }
47 sub SORTBY { 20004 }
48
49 # The "Event" package contains constants returned by last_event()
50 package ZOOM::Event;
51 sub NONE { Net::Z3950::ZOOM::EVENT_NONE }
52 sub CONNECT { Net::Z3950::ZOOM::EVENT_CONNECT }
53 sub SEND_DATA { Net::Z3950::ZOOM::EVENT_SEND_DATA }
54 sub RECV_DATA { Net::Z3950::ZOOM::EVENT_RECV_DATA }
55 sub TIMEOUT { Net::Z3950::ZOOM::EVENT_TIMEOUT }
56 sub UNKNOWN { Net::Z3950::ZOOM::EVENT_UNKNOWN }
57 sub SEND_APDU { Net::Z3950::ZOOM::EVENT_SEND_APDU }
58 sub RECV_APDU { Net::Z3950::ZOOM::EVENT_RECV_APDU }
59 sub RECV_RECORD { Net::Z3950::ZOOM::EVENT_RECV_RECORD }
60 sub RECV_SEARCH { Net::Z3950::ZOOM::EVENT_RECV_SEARCH }
61
62 # ----------------------------------------------------------------------------
63
64 package ZOOM;
65
66 sub diag_str {
67     my($code) = @_;
68
69     # Special cases for error specific to the OO layer
70     if ($code == ZOOM::Error::CREATE_QUERY) {
71         return "can't create query object";
72     } elsif ($code == ZOOM::Error::QUERY_CQL) {
73         return "can't set CQL query";
74     } elsif ($code == ZOOM::Error::QUERY_PQF) {
75         return "can't set prefix query";
76     } elsif ($code == ZOOM::Error::SORTBY) {
77         return "can't set sort-specification";
78     }
79
80     return Net::Z3950::ZOOM::diag_str($code);
81 }
82
83 ### More of the ZOOM::Exception instantiations should use this
84 sub _oops {
85     my($code, $addinfo) = @_;
86
87     die new ZOOM::Exception($code, diag_str($code), $addinfo);
88 }
89
90 # ----------------------------------------------------------------------------
91
92 package ZOOM::Exception;
93
94 sub new {
95     my $class = shift();
96     my($code, $message, $addinfo) = @_;
97     ### support diag-set, too
98
99     return bless {
100         code => $code,
101         message => $message,
102         addinfo => $addinfo,
103     }, $class;
104 }
105
106 sub code {
107     my $this = shift();
108     return $this->{code};
109 }
110
111 sub message {
112     my $this = shift();
113     return $this->{message};
114 }
115
116 sub addinfo {
117     my $this = shift();
118     return $this->{addinfo};
119 }
120
121
122 # ----------------------------------------------------------------------------
123
124 package ZOOM::Options;
125
126 sub new {
127     my $class = shift();
128     my($p1, $p2) = @_;
129
130     my $opts;
131     if (@_ == 0) {
132         $opts = Net::Z3950::ZOOM::options_create();
133     } elsif (@_ == 1) {
134         $opts = Net::Z3950::ZOOM::options_create_with_parent($p1->_opts());
135     } elsif (@_ == 2) {
136         $opts = Net::Z3950::ZOOM::options_create_with_parent2($p1->_opts(),
137                                                               $p2->_opts());
138     } else {
139         die "can't make $class object with more than 2 parents";
140     }
141
142     return bless {
143         _opts => $opts,
144     }, $class;
145 }
146
147 sub _opts {
148     my $this = shift();
149
150     my $_opts = $this->{_opts};
151     die "{_opts} undefined: has this Options block been destroy()ed?"
152         if !defined $_opts;
153
154     return $_opts;
155 }
156
157 sub option {
158     my $this = shift();
159     my($key, $value) = @_;
160
161     my $oldval = Net::Z3950::ZOOM::options_get($this->_opts(), $key);
162     Net::Z3950::ZOOM::options_set($this->_opts(), $key, $value)
163         if defined $value;
164
165     return $oldval;
166 }
167
168 sub option_binary {
169     my $this = shift();
170     my($key, $value) = @_;
171
172     my $dummylen = 0;
173     my $oldval = Net::Z3950::ZOOM::options_getl($this->_opts(),
174                                                 $key, $dummylen);
175     Net::Z3950::ZOOM::options_setl($this->_opts(), $key,
176                                    $value, length($value))
177         if defined $value;
178
179     return $oldval;
180 }
181
182 # This is a bit stupid, since the scalar values that Perl returns from
183 # option() can be used as a boolean; but it's just possible that some
184 # applications will rely on ZOOM_options_get_bool()'s idiosyncratic
185 # interpretation of what constitutes truth.
186 #
187 sub bool {
188     my $this = shift();
189     my($key, $default) = @_;
190
191     return Net::Z3950::ZOOM::options_get_bool($this->_opts(), $key, $default);
192 }
193
194 # .. and the next two are even more stupid
195 sub int {
196     my $this = shift();
197     my($key, $default) = @_;
198
199     return Net::Z3950::ZOOM::options_get_int($this->_opts(), $key, $default);
200 }
201
202 sub set_int {
203     my $this = shift();
204     my($key, $value) = @_;
205
206     Net::Z3950::ZOOM::options_set_int($this->_opts(), $key, $value);
207 }
208
209 #   ### Feel guilty.  Feel very, very guilty.  I've not been able to
210 #       get the callback memory-management right in "ZOOM.xs", with
211 #       the result that the values of $function and $udata passed into
212 #       this function, which are on the stack, have sometimes been
213 #       freed by the time they're used by __ZOOM_option_callback(),
214 #       with hilarious results.  To avoid this, I copy the values into
215 #       module-scoped globals, and pass _those_ into the extension
216 #       function.  To avoid overwriting those globals by subsequent
217 #       calls, I keep all the old ones, pushed onto the @_function and
218 #       @_udata arrays, which means that THIS FUNCTION LEAKS MEMORY
219 #       LIKE IT'S GOING OUT OF FASHION.  Not nice.  One day, I should
220 #       fix this, but for now there's more important fish to fry.
221 #
222 my(@_function, @_udata);
223 sub set_callback {
224     my $o1 = shift();
225     my($function, $udata) = @_;
226
227     push @_function, $function;
228     push @_udata, $udata;
229     Net::Z3950::ZOOM::options_set_callback($o1->_opts(),
230                                            $_function[-1], $_udata[-1]);
231 }
232
233 sub destroy {
234     my $this = shift();
235
236     Net::Z3950::ZOOM::options_destroy($this->_opts());
237     $this->{_opts} = undef;
238 }
239
240
241 # ----------------------------------------------------------------------------
242
243 package ZOOM::Connection;
244
245 sub new {
246     my $class = shift();
247     my($host, $port) = @_;
248
249     my $_conn = Net::Z3950::ZOOM::connection_new($host, $port);
250     my($errcode, $errmsg, $addinfo) = (undef, "dummy", "dummy");
251     $errcode = Net::Z3950::ZOOM::connection_error($_conn, $errmsg, $addinfo);
252     die new ZOOM::Exception($errcode, $errmsg, $addinfo) if $errcode;
253
254     return bless {
255         host => $host,
256         port => $port,
257         _conn => $_conn,
258     };
259 }
260
261 sub create {
262     my $class = shift();
263     my($options) = @_;
264
265     my $_conn = Net::Z3950::ZOOM::connection_create($options->_opts());
266     return bless {
267         host => undef,
268         port => undef,
269         _conn => $_conn,
270     };
271 }
272
273 # PRIVATE within this class
274 sub _conn {
275     my $this = shift();
276
277     my $_conn = $this->{_conn};
278     die "{_conn} undefined: has this Connection been destroy()ed?"
279         if !defined $_conn;
280
281     return $_conn;
282 }
283
284 sub error_x {
285     my $this = shift();
286
287     my($errcode, $errmsg, $addinfo, $diagset) = (undef, "dummy", "dummy", "d");
288     $errcode = Net::Z3950::ZOOM::connection_error_x($this->_conn(), $errmsg,
289                                                     $addinfo, $diagset);
290     return ($errcode, $errmsg, $addinfo, $diagset);
291 }
292
293 sub errcode {
294     my $this = shift();
295     return Net::Z3950::ZOOM::connection_errcode($this->_conn());
296 }
297
298 sub errmsg {
299     my $this = shift();
300     return Net::Z3950::ZOOM::connection_errmsg($this->_conn());
301 }
302
303 sub addinfo {
304     my $this = shift();
305     return Net::Z3950::ZOOM::connection_addinfo($this->_conn());
306 }
307
308 sub connect {
309     my $this = shift();
310     my($host, $port) = @_;
311
312     Net::Z3950::ZOOM::connection_connect($this->_conn(), $host, $port);
313     my($errcode, $errmsg, $addinfo) = (undef, "dummy", "dummy");
314     $errcode = Net::Z3950::ZOOM::connection_error($this->_conn(),
315                                                   $errmsg, $addinfo);
316     die new ZOOM::Exception($errcode, $errmsg, $addinfo) if $errcode;
317     # No return value
318 }
319
320 sub option {
321     my $this = shift();
322     my($key, $value) = @_;
323
324     my $oldval = Net::Z3950::ZOOM::connection_option_get($this->_conn(), $key);
325     Net::Z3950::ZOOM::connection_option_set($this->_conn(), $key, $value)
326         if defined $value;
327
328     return $oldval;
329 }
330
331 sub option_binary {
332     my $this = shift();
333     my($key, $value) = @_;
334
335     my $dummylen = 0;
336     my $oldval = Net::Z3950::ZOOM::connection_option_getl($this->_conn(),
337                                                           $key, $dummylen);
338     Net::Z3950::ZOOM::connection_option_setl($this->_conn(), $key,
339                                              $value, length($value))
340         if defined $value;
341
342     return $oldval;
343 }
344
345 sub search {
346     my $this = shift();
347     my($query) = @_;
348
349     my $_rs = Net::Z3950::ZOOM::connection_search($this->_conn(),
350                                                   $query->_query());
351     my($errcode, $errmsg, $addinfo) = (undef, "dummy", "dummy");
352     $errcode = Net::Z3950::ZOOM::connection_error($this->_conn(),
353                                                   $errmsg, $addinfo);
354     die new ZOOM::Exception($errcode, $errmsg, $addinfo) if $errcode;
355
356     return _new ZOOM::ResultSet($this, $query, $_rs);
357 }
358
359 sub search_pqf {
360     my $this = shift();
361     my($pqf) = @_;
362
363     my $_rs = Net::Z3950::ZOOM::connection_search_pqf($this->_conn(), $pqf);
364     my($errcode, $errmsg, $addinfo) = (undef, "dummy", "dummy");
365     $errcode = Net::Z3950::ZOOM::connection_error($this->_conn(),
366                                                   $errmsg, $addinfo);
367     die new ZOOM::Exception($errcode, $errmsg, $addinfo) if $errcode;
368
369     return _new ZOOM::ResultSet($this, $pqf, $_rs);
370 }
371
372 sub destroy {
373     my $this = shift();
374
375     Net::Z3950::ZOOM::connection_destroy($this->_conn());
376     $this->{_conn} = undef;
377 }
378
379
380 # ----------------------------------------------------------------------------
381
382 package ZOOM::Query;
383
384 sub new {
385     my $class = shift();
386     die "You can't create $class objects: it's a virtual base class";
387 }
388
389 sub _query {
390     my $this = shift();
391
392     my $_query = $this->{_query};
393     die "{_query} undefined: has this Query been destroy()ed?"
394         if !defined $_query;
395
396     return $_query;
397 }
398
399 sub sortby {
400     my $this = shift();
401     my($sortby) = @_;
402
403     Net::Z3950::ZOOM::query_sortby($this->_query(), $sortby) == 0
404         or ZOOM::_oops(ZOOM::Error::SORTBY, $sortby);
405 }
406
407 sub destroy {
408     my $this = shift();
409
410     Net::Z3950::ZOOM::query_destroy($this->_query());
411     $this->{_query} = undef;
412 }
413
414
415 package ZOOM::Query::CQL;
416 our @ISA = qw(ZOOM::Query);
417
418 sub new {
419     my $class = shift();
420     my($string) = @_;
421
422     my $q = Net::Z3950::ZOOM::query_create()
423         or ZOOM::_oops(ZOOM::Error::CREATE_QUERY);
424     Net::Z3950::ZOOM::query_cql($q, $string) == 0
425         or ZOOM::_oops(ZOOM::Error::QUERY_CQL, $string);
426
427     return bless {
428         _query => $q,
429     }, $class;
430 }
431
432
433 package ZOOM::Query::PQF;
434 our @ISA = qw(ZOOM::Query);
435
436 sub new {
437     my $class = shift();
438     my($string) = @_;
439
440     my $q = Net::Z3950::ZOOM::query_create()
441         or ZOOM::_oops(ZOOM::Error::CREATE_QUERY);
442     Net::Z3950::ZOOM::query_prefix($q, $string) == 0
443         or ZOOM::_oops(ZOOM::Error::QUERY_PQF, $string);
444
445     return bless {
446         _query => $q,
447     }, $class;
448 }
449
450
451 # ----------------------------------------------------------------------------
452
453 package ZOOM::ResultSet;
454
455 sub new {
456     my $class = shift();
457     die "You can't create $class objects directly";
458 }
459
460 # PRIVATE to ZOOM::Connection::search()
461 sub _new {
462     my $class = shift();
463     my($conn, $query, $_rs) = @_;
464
465     return bless {
466         conn => $conn,
467         query => $query,        # This is not currently used, which is
468                                 # just as well since it could be
469                                 # either a string (when the RS is
470                                 # created with search_pqf()) or a
471                                 # ZOOM::Query object (when it's
472                                 # created with search())
473         _rs => $_rs,
474     }, $class;
475 }
476
477 # PRIVATE within this class
478 sub _rs {
479     my $this = shift();
480
481     my $_rs = $this->{_rs};
482     die "{_rs} undefined: has this ResultSet been destroy()ed?"
483         if !defined $_rs;
484
485     return $_rs;
486 }
487
488 sub size {
489     my $this = shift();
490
491     return Net::Z3950::ZOOM::resultset_size($this->_rs());
492 }
493
494 sub record {
495     my $this = shift();
496     my($which) = @_;
497
498     my $_rec = Net::Z3950::ZOOM::resultset_record($this->_rs(), $which);
499     ### Check for error -- but how?
500
501     # For some reason, I have to use the explicit "->" syntax in order
502     # to invoke the ZOOM::Record constructor here, even though I don't
503     # have to do the same for _new ZOOM::ResultSet above.  Weird.
504     return ZOOM::Record->_new($this, $which, $_rec);
505 }
506
507 sub destroy {
508     my $this = shift();
509
510     Net::Z3950::ZOOM::resultset_destroy($this->_rs());
511     $this->{_rs} = undef;
512 }
513
514
515 # ----------------------------------------------------------------------------
516
517 package ZOOM::Record;
518
519 sub new {
520     my $class = shift();
521     die "You can't create $class objects directly";
522 }
523
524 # PRIVATE to ZOOM::ResultSet::record()
525 sub _new {
526     my $class = shift();
527     my($rs, $which, $_rec) = @_;
528
529     return bless {
530         rs => $rs,
531         which => $which,
532         _rec => $_rec,
533     }, $class;
534 }
535
536 # PRIVATE within this class
537 sub _rec {
538     my $this = shift();
539
540     return $this->{_rec};
541 }
542
543 sub render {
544     my $this = shift();
545
546     my $len = 0;
547     my $string = Net::Z3950::ZOOM::record_get($this->_rec(), "render", $len);
548     # I don't think we need '$len' at all.  ### Probably the Perl-to-C
549     # glue code should use the value of `len' as well as the opaque
550     # data-pointer returned, to ensure that the SV contains all of the
551     # returned data and does not stop at the first NUL character in
552     # binary data.  Carefully check the ZOOM_record_get() documentation.
553     return $string;
554 }
555
556 sub raw {
557     my $this = shift();
558
559     my $len = 0;
560     my $string = Net::Z3950::ZOOM::record_get($this->_rec(), "raw", $len);
561     # See comment about $len in render()
562     return $string;
563 }
564
565
566 1;