From 8e0f7888835eb1a84882b440209ec125ce5856c1 Mon Sep 17 00:00:00 2001 From: Mike Taylor Date: Thu, 8 Aug 2002 13:31:54 +0000 Subject: [PATCH] Added ZOOM. --- zoom/Changes | 38 +++++++++++++++ zoom/Makefile | 36 +++++++++++++++ zoom/README | 28 +++++++++++ zoom/master-header | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++++ zoom/zclient.cpp | 54 ++++++++++++++++++++++ zoom/zconn.cpp | 34 ++++++++++++++ zoom/zerr.cpp | 93 +++++++++++++++++++++++++++++++++++++ zoom/zquery.cpp | 54 ++++++++++++++++++++++ zoom/zrec.cpp | 69 +++++++++++++++++++++++++++ zoom/zrs.cpp | 59 +++++++++++++++++++++++ 10 files changed, 596 insertions(+) create mode 100644 zoom/Changes create mode 100644 zoom/Makefile create mode 100644 zoom/README create mode 100644 zoom/master-header create mode 100644 zoom/zclient.cpp create mode 100644 zoom/zconn.cpp create mode 100644 zoom/zerr.cpp create mode 100644 zoom/zquery.cpp create mode 100644 zoom/zrec.cpp create mode 100644 zoom/zrs.cpp diff --git a/zoom/Changes b/zoom/Changes new file mode 100644 index 0000000..266869a --- /dev/null +++ b/zoom/Changes @@ -0,0 +1,38 @@ +Changes between the current version of the C++ binding specification +(http://zoom.z3950.org/bind/cplusplus/zoom-1.0g.hh) and the +specification generated in "interface.h" in this directory (which I +expect to become version 1.3a of the official specification.) + +-- + +Add #include for size_t + +Add comment about G++'s rejection of throw(ZOOM:error) clause + +Add destructor declaration to connection class. + +Remove errcode(), errmsg() and addinfo() methods from the connection +and resultSet classes, since exceptions should be used in these +enlightened days. + +Rename the record::recordSyntax enumeration to record::syntax, and add +an UNKNOWN element. + +Remove "virtual" from all the record class's methods, including its +destructor, since we no longer expect to derive record subclasses +representing records expressed in specific record-syntaxes -- see +version 1.3 of the ZOOM AAPI. + +Remove the nfields() and field() methods from the record class -- +again, see v1.3 of the AAPI. + +Add some substance to the error base class: it can now be created +(with an error-code specified), and the error-code may be both fetched +and rendered as a human-readable string. This is necessary so that +it's possible to meaningfully catch(error e). + +Add the missing char *errmsg() method to the systemError and bib1Error +classes. + +Add a new error subclass, queryError, for reporting malformed query +strings etc. diff --git a/zoom/Makefile b/zoom/Makefile new file mode 100644 index 0000000..bdcc128 --- /dev/null +++ b/zoom/Makefile @@ -0,0 +1,36 @@ +# $Header: /home/cvsroot/yaz++/zoom/Attic/Makefile,v 1.1 2002-08-08 13:31:54 mike Exp $ + +CCC = g++ # ... until I figure out what the standard + # Make macro name is for the C++ compiler. +CPPFLAGS := -Wall -g +L = libzoom.a +OBJ = $L(zerr.o) $L(zconn.o) $L(zquery.o) $L(zrs.o) $L(zrec.o) + +all: interface.h zclient + +zclient: zclient.o $L + @echo CCC = $(CCC) + $(CCC) $(CPPFLAGS) -o zclient zclient.o $L -lyaz + +test: zclient + ./zclient bagel.indexdata.dk 210 gils '@and mineral epicenter' + +$L: $(OBJ) + ranlib $L + +$(OBJ): zoom++.h + +zclient.o: zoom++.h + +zoom++.h: master-header + rm -f $@ + sed 's/^* / /; s/^*/ /' $< > $@ + chmod -w $@ + +interface.h: master-header + rm -f $@ + grep -v '^*' $< > $@ + chmod -w $@ + +clean: + rm -f zoom++.h interface.h zclient *.[ao] core diff --git a/zoom/README b/zoom/README new file mode 100644 index 0000000..729a4d6 --- /dev/null +++ b/zoom/README @@ -0,0 +1,28 @@ +This is an initial implementation of the ZOOM C++ binding +(http://zoom.z3950.org/bind/cplusplus/) for the Yaz toolkit. +It's a rather obvious thin layer on top of Yaz's ZOOM-C +implementation. + +The build environment will no doubt need to be tweaked to fit +in with the way that the rest of Yaz++ is built. + +Only one wrinkle, really: we want the ZOOM C++ header file for two +different purposes: one is to function as an interface specification +that can go on the ZOOM web-site, and one is to actually build +against. The requirements of these two manifestations of the header +file are rather different in that the latter needs to include +implementation details that the former very explicitly does _not_ +want. Accordingly, we automatically generate both versions from a +master copy in which the implementation-dependent lines are preceded +by asterisks(*). So we have: + + master-header The master copy, which may be edited. + interface.h The read-only, automatically-generated file + that can be considered a formal specification + of the ZOOM C++ interface. + zoom++.h The read-only, automatically-generated file + that is actually used in the build process, + and ought quite possibly to be moved into + ../include/yaz++/zoom.h + +Good luck! diff --git a/zoom/master-header b/zoom/master-header new file mode 100644 index 0000000..c573e33 --- /dev/null +++ b/zoom/master-header @@ -0,0 +1,131 @@ +// $Header: /home/cvsroot/yaz++/zoom/master-header,v 1.1 2002-08-08 13:31:54 mike Exp $ +// +// ZOOM C++ Binding. +// The ZOOM homepage is at http://zoom.z3950.org/ +// +// Derived from version 1.0g at +// http://zoom.z3950.org/bind/cplusplus/zoom-1.0g.hh + +#include // for size_t + +*/* +* * This is a bit stupid. The fact that our ZOOM-C++ implementation is +* * based on the ZOOM-C implementation is our Dirty Little Secret, and +* * there is in principle no reason why client code need be bothered +* * with it. Except of course that the public class declarations in +* * C++ have to lay their private parts out for the world to see +* * (oo-er). Hence the inclusion of +* */ +*#include +* +namespace ZOOM { + // Forward declarations for type names. + class query; + class resultSet; + class record; + + const char *option (const char *key); + const char *option (const char *key, const char *val); + int errcode (); + char *errmsg (); + char *addinfo (); + + class connection { +* ZOOM_connection c; + public: + connection (const char *hostname, int portnum); + // ### I would like to add a ``throw (ZOOM::error)'' clause + // here, but it looks like G++ 2.95.2 doesn't recognise it. + ~connection (); + const char *option (const char *key) const; + const char *option (const char *key, const char *val); +* ZOOM_connection _getYazConnection() const { return c; } // package-private + }; + + class query { + // pure virtual class: derive concrete subclasses from it. +* protected: +* ZOOM_query q; + public: + virtual ~query (); +* ZOOM_query _getYazQuery() const { return q; } // package-private + }; + + class prefixQuery : public query { + public: + prefixQuery (const char *pqn); + ~prefixQuery(); + }; + + class CCLQuery : public query { + public: + CCLQuery (const char *ccl, void *qualset); + ~CCLQuery(); + }; + + class resultSet { +* connection &owner; +* ZOOM_resultset rs; + public: + resultSet (connection &c, const query &q); + ~resultSet (); + const char *option (const char *key) const; + const char *option (const char *key, const char *val); + size_t size () const; + const record *getRecord (size_t i) const; + }; + + class record { +* const resultSet *owner; +* ZOOM_record r; + public: +* record::record(const resultSet *rs, ZOOM_record rec): +* owner(rs), r(rec) {} + ~record (); + enum syntax { + UNKNOWN, GRS1, SUTRS, USMARC, UKMARC, XML + }; + record *clone () const; + syntax recsyn () const; + const char *render () const; + const char *rawdata () const; + }; + + class error { +* protected: +* int code; + public: + error (int code); + int errcode () const; + const char *errmsg () const; + }; + + class systemError: public error { + public: + systemError (); + int errcode () const; + const char *errmsg () const; + }; + + class bib1Error: public error { +* const char *info; + public: +* ~bib1Error(); + bib1Error (int errcode, const char *addinfo); + int errcode () const; + const char *errmsg () const; + const char *addinfo () const; + }; + + class queryError: public error { +* const char *q; + public: +* ~queryError(); + static const int PREFIX = 1; + static const int CCL = 2; + queryError (int qtype, const char *source); + int errcode () const; + const char *errmsg () const; + const char *addinfo () const; + }; +} diff --git a/zoom/zclient.cpp b/zoom/zclient.cpp new file mode 100644 index 0000000..8a1bb95 --- /dev/null +++ b/zoom/zclient.cpp @@ -0,0 +1,54 @@ +// $Header: /home/cvsroot/yaz++/zoom/zclient.cpp,v 1.1 2002-08-08 13:31:54 mike Exp $ + +// Trivial sample client + +#include // for atoi() +#include +#include "zoom++.h" + +int main(int argc, char **argv) +{ + if (argc != 5) { + cerr << "Usage: " << argv[0] << + " <@prefix-search>\n"; + return 1; + } + + const char *hostname = argv[1]; + const int port = atoi(argv[2]); + const char *dbname = argv[3]; + const char *searchSpec = argv[4]; + + ZOOM::connection *conn; + try { + conn = new ZOOM::connection(hostname, port); + } catch(ZOOM::bib1Error err) { + cerr << argv[0] << ": connect: " << + err.errmsg() << " (" << err.addinfo() << ")\n"; + return 2; + } catch(ZOOM::error err) { + cerr << argv[0] << ": connect: " << err.errmsg() << "\n"; + return 2; + } + + conn->option("databaseName", dbname); + ZOOM::prefixQuery pq(searchSpec); + ZOOM::resultSet *rs; + try { + rs = new ZOOM::resultSet(*conn, pq); + } catch(ZOOM::bib1Error err) { + cerr << argv[0] << ": searchSpec: " << + err.errmsg() << " (" << err.addinfo() << ")\n"; + return 3; + } + + size_t n = rs->size(); + cout << "found " << n << " records:\n"; + for (size_t i = 0; i < n; i++) { + const ZOOM::record *rec = rs->getRecord(i); + cout << "=== record " << i+1 << " (recsyn " << rec->recsyn() + << ") ===\n" << rec->render(); + } + + return 0; +} diff --git a/zoom/zconn.cpp b/zoom/zconn.cpp new file mode 100644 index 0000000..e6f9dc9 --- /dev/null +++ b/zoom/zconn.cpp @@ -0,0 +1,34 @@ +// $Header: /home/cvsroot/yaz++/zoom/zconn.cpp,v 1.1 2002-08-08 13:31:54 mike Exp $ + +// Z39.50 Connection class + +#include "zoom++.h" + + +namespace ZOOM { + connection::connection(const char *hostname, int portnum) { + c = ZOOM_connection_new(hostname, portnum); + + int errcode; + const char *errmsg; // unused: carries same info as `errcode' + const char *addinfo; + if ((errcode = ZOOM_connection_error(c, &errmsg, &addinfo)) != 0) { + throw bib1Error(errcode, addinfo); + } + } + + const char *connection::option(const char *key) const { + return ZOOM_connection_option_get(c, key); + } + + const char *connection::option(const char *key, const char *val) { + // ### There may be memory-management issues here. + const char *old = ZOOM_connection_option_get(c, key); + ZOOM_connection_option_set(c, key, val); + return old; + } + + connection::~connection() { + ZOOM_connection_destroy(c); + } +} diff --git a/zoom/zerr.cpp b/zoom/zerr.cpp new file mode 100644 index 0000000..e418003 --- /dev/null +++ b/zoom/zerr.cpp @@ -0,0 +1,93 @@ +// $Header: /home/cvsroot/yaz++/zoom/Attic/zerr.cpp,v 1.1 2002-08-08 13:31:54 mike Exp $ + +// Z39.50 Error classes + +#include +#include // for strerror(), strlen(), strcpy() +#include // for sprintf() +#include +#include "zoom++.h" + + +namespace ZOOM { + error::error(int errcode) { + code = errcode; + } + + int error::errcode() const { + return code; + } + + const char *error::errmsg() const { + static char buf[40]; + sprintf(buf, "error #%d", code); + return buf; + } + + + + systemError::systemError() : error::error(errno){ + code = errno; + } + + int systemError::errcode() const { + return code; + } + + const char *systemError::errmsg() const { + return strerror(code); + } + + + + bib1Error::bib1Error(int errcode, const char *addinfo) : + error::error(errcode) { + info = new char[strlen(addinfo)+1]; + strcpy((char*) info, addinfo); + } + + bib1Error::~bib1Error() { + delete info; + } + + int bib1Error::errcode() const { + return code; + } + + const char *bib1Error::errmsg() const { + return diagbib1_str(code); + } + + const char *bib1Error::addinfo() const { + return info; + } + + + + queryError::queryError(int qtype, const char *source) : + error::error(qtype) { + q = new char[strlen(source)+1]; + strcpy((char*) q, source); + } + + queryError::~queryError() { + delete q; + } + + int queryError::errcode() const { + return code; + } + + const char *queryError::errmsg() const { + switch (code) { + case PREFIX: return "bad prefix search"; + case CCL: return "bad CCL search"; + default: break; + } + return "bad search (unknown type)"; + } + + const char *queryError::addinfo() const { + return q; + } +} diff --git a/zoom/zquery.cpp b/zoom/zquery.cpp new file mode 100644 index 0000000..77c0d93 --- /dev/null +++ b/zoom/zquery.cpp @@ -0,0 +1,54 @@ +// $Header: /home/cvsroot/yaz++/zoom/zquery.cpp,v 1.1 2002-08-08 13:31:54 mike Exp $ + +// Z39.50 Query classes + +#include "zoom++.h" + + +namespace ZOOM { + query::~query() { + ZOOM_query_destroy(q); + q = 0; + } + + + + prefixQuery::prefixQuery(const char *pqn) { + q = ZOOM_query_create(); + if (ZOOM_query_prefix(q, pqn) == -1) { + ZOOM_query_destroy(q); + throw queryError(queryError::PREFIX, pqn); + } + } + + // The binding specification says we have to have destructors for + // the query subclasses, so in they go -- even though they don't + // actually _do_ anything that inheriting the base query type's + // destructor wouldn't do. It's an irritant of C++ that the + // declaration of a subclass has to express explicitly the + // implementation detail of whether destruction is implemented + // by a specific destructor or by inheritance. Oh well. + // + // ### Not sure whether I need to do nothing at all, and the + // superclass destructor gets called anyway (I think that only + // works when you _don't_ define a destructor so that the default + // one pertains) or whether I need to duplicate the functionality + // of that destructor. Let's play safe by assuming the latter and + // zeroing what we free so that we get bitten if we're wrong. + // + prefixQuery::~prefixQuery() { + ZOOM_query_destroy(q); + q = 0; + } + + + + CCLQuery::CCLQuery(const char *ccl, void *qualset) { + throw "Oops. No CCL support in ZOOM-C yet. Sorry."; + } + + CCLQuery::~CCLQuery() { + ZOOM_query_destroy(q); + q = 0; + } +} diff --git a/zoom/zrec.cpp b/zoom/zrec.cpp new file mode 100644 index 0000000..18e34be --- /dev/null +++ b/zoom/zrec.cpp @@ -0,0 +1,69 @@ +// $Header: /home/cvsroot/yaz++/zoom/zrec.cpp,v 1.1 2002-08-08 13:31:54 mike Exp $ + +// Z39.50 Record class + +#include "zoom++.h" +#include // for strcasecmp() + + +namespace ZOOM { + record::~record() { + if (owner == 0) { + // Must have been clone()d + ZOOM_record_destroy(r); + } + } + + // ### Would this operation be better expressed as a copy constructor? + record *record::clone() const { + // It's tempting just to replace `r' with a clone, and return + // `this', but probably more honest to allocate a new C++ + // record object. + + record *rec = new record(0, 0); + if ((rec->r = ZOOM_record_clone(r)) == 0) { + // Presumably an out-of-memory error + throw systemError(); + } + + return rec; + } + + // It's tempting to modify this method just to return either the + // string that ZOOM_record_get("syntax") gives us, or the VAL_* + // value from Yaz's OID database, but we'd break the nominal + // plug-compatibility of competing C++ binding implementations + // if we did that. + // + record::syntax record::recsyn() const { + const char *syn = ZOOM_record_get(r, "syntax", 0); + + // These string constants are from yaz/util/oid.c + if (!strcasecmp(syn, "xml")) + return XML; + else if (!strcasecmp(syn, "GRS-1")) + return GRS1; + else if (!strcasecmp(syn, "SUTRS")) + return SUTRS; + else if (!strcasecmp(syn, "USmarc")) + return USMARC; + else if (!strcasecmp(syn, "UKmarc")) + return UKMARC; + else if (!strcasecmp(syn, "XML") || + !strcasecmp(syn, "text-XML") || + !strcasecmp(syn, "application-XML")) + return XML; + + return UNKNOWN; + } + + const char *record::render() const { + int len; + return ZOOM_record_get(r, "render", &len); + } + + const char *record::rawdata() const { + int len; + return ZOOM_record_get(r, "raw", &len); + } +} diff --git a/zoom/zrs.cpp b/zoom/zrs.cpp new file mode 100644 index 0000000..39d0422 --- /dev/null +++ b/zoom/zrs.cpp @@ -0,0 +1,59 @@ +// $Header: /home/cvsroot/yaz++/zoom/zrs.cpp,v 1.1 2002-08-08 13:31:54 mike Exp $ + +// Z39.50 Result Set class + +#include "zoom++.h" + + +namespace ZOOM { + resultSet::resultSet(connection &c, const query &q) : owner(c) { + ZOOM_connection yazc = c._getYazConnection(); + rs = ZOOM_connection_search(yazc, q._getYazQuery()); + int errcode; + const char *errmsg; // unused: carries same info as `errcode' + const char *addinfo; + + if ((errcode = ZOOM_connection_error(yazc, &errmsg, &addinfo)) != 0) { + throw bib1Error(errcode, addinfo); + } + } + + resultSet::~resultSet() { + ZOOM_resultset_destroy(rs); + } + + const char *resultSet::option(const char *key) const { + return ZOOM_resultset_option_get(rs, key); + } + + const char *resultSet::option(const char *key, const char *val) { + // ### There may be memory-management issues here. + const char *old = ZOOM_resultset_option_get(rs, key); + ZOOM_resultset_option_set(rs, key, val); + return old; + } + + size_t resultSet::size() const { + return ZOOM_resultset_size(rs); + } + + const record *resultSet::getRecord(size_t i) const { + ZOOM_record rec; + if ((rec = ZOOM_resultset_record(rs, i)) == 0) { + const char *errmsg; // unused: carries same info as `errcode' + const char *addinfo; + int errcode = ZOOM_connection_error(owner._getYazConnection(), + &errmsg, &addinfo); + throw bib1Error(errcode, addinfo); + } + + // Memory management is odd here. The ZOOM-C record we've + // just fetched (`rec') is owned by the ZOOM-C result-set we + // fetched it from (`rs'), so all we need to allocate is a + // ZOOM-C++ wrapper for it, which is destroyed at the + // appropriate time -- but the underlying (ZOOM-C) record is + // _not_ destroyed at that time, because it's done when the + // underlying result-set is deleted. + return new record(this, rec); + } +} -- 1.7.10.4