zoom: Handle authentication element
[metaproxy-moved-to-github.git] / src / filter_zoom.cpp
1 /* This file is part of Metaproxy.
2    Copyright (C) 2005-2011 Index Data
3
4 Metaproxy is free software; you can redistribute it and/or modify it under
5 the terms of the GNU General Public License as published by the Free
6 Software Foundation; either version 2, or (at your option) any later
7 version.
8
9 Metaproxy is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17 */
18
19 #include "config.hpp"
20 #include "filter_zoom.hpp"
21 #include <yaz/zoom.h>
22 #include <metaproxy/package.hpp>
23 #include <metaproxy/util.hpp>
24 #include "torus.hpp"
25
26 #include <libxslt/xsltutils.h>
27 #include <libxslt/transform.h>
28
29 #include <boost/thread/mutex.hpp>
30 #include <boost/thread/condition.hpp>
31 #include <yaz/ccl.h>
32 #include <yaz/cql.h>
33 #include <yaz/oid_db.h>
34 #include <yaz/diagbib1.h>
35 #include <yaz/log.h>
36 #include <yaz/zgdu.h>
37 #include <yaz/querytowrbuf.h>
38
39 namespace mp = metaproxy_1;
40 namespace yf = mp::filter;
41
42 namespace metaproxy_1 {
43     namespace filter {
44         struct Zoom::Searchable : boost::noncopyable {
45             std::string authentication;
46             std::string database;
47             std::string target;
48             std::string query_encoding;
49             std::string sru;
50             std::string request_syntax;
51             std::string element_set;
52             std::string record_encoding;
53             std::string transform_xsl_fname;
54             bool use_turbomarc;
55             bool piggyback;
56             CCL_bibset ccl_bibset;
57             Searchable();
58             ~Searchable();
59         };
60         class Zoom::Backend : boost::noncopyable {
61             friend class Impl;
62             friend class Frontend;
63             std::string zurl;
64             ZOOM_connection m_connection;
65             ZOOM_resultset m_resultset;
66             std::string m_frontend_database;
67             SearchablePtr sptr;
68             xsltStylesheetPtr xsp;
69         public:
70             Backend(SearchablePtr sptr);
71             ~Backend();
72             void connect(std::string zurl, int *error, const char **addinfo);
73             void search_pqf(const char *pqf, Odr_int *hits,
74                             int *error, const char **addinfo);
75             void present(Odr_int start, Odr_int number, ZOOM_record *recs,
76                          int *error, const char **addinfo);
77             void set_option(const char *name, const char *value);
78             int get_error(const char **addinfo);
79         };
80         class Zoom::Frontend : boost::noncopyable {
81             friend class Impl;
82             Impl *m_p;
83             bool m_is_virtual;
84             bool m_in_use;
85             yazpp_1::GDU m_init_gdu;
86             BackendPtr m_backend;
87             void handle_package(mp::Package &package);
88             void handle_search(mp::Package &package);
89             void handle_present(mp::Package &package);
90             BackendPtr get_backend_from_databases(std::string &database,
91                                                   int *error,
92                                                   const char **addinfo);
93             Z_Records *get_records(Odr_int start,
94                                    Odr_int number_to_present,
95                                    int *error,
96                                    const char **addinfo,
97                                    Odr_int *number_of_records_returned,
98                                    ODR odr, BackendPtr b,
99                                    Odr_oid *preferredRecordSyntax,
100                                    const char *element_set_name);
101         public:
102             Frontend(Impl *impl);
103             ~Frontend();
104         };
105         class Zoom::Impl {
106             friend class Frontend;
107         public:
108             Impl();
109             ~Impl();
110             void process(metaproxy_1::Package & package);
111             void configure(const xmlNode * ptr, bool test_only);
112         private:
113             FrontendPtr get_frontend(mp::Package &package);
114             void release_frontend(mp::Package &package);
115             void parse_torus(const xmlNode *ptr);
116
117             std::list<Zoom::SearchablePtr>m_searchables;
118
119             std::map<mp::Session, FrontendPtr> m_clients;            
120             boost::mutex m_mutex;
121             boost::condition m_cond_session_ready;
122             mp::Torus torus;
123         };
124     }
125 }
126
127 // define Pimpl wrapper forwarding to Impl
128  
129 yf::Zoom::Zoom() : m_p(new Impl)
130 {
131 }
132
133 yf::Zoom::~Zoom()
134 {  // must have a destructor because of boost::scoped_ptr
135 }
136
137 void yf::Zoom::configure(const xmlNode *xmlnode, bool test_only)
138 {
139     m_p->configure(xmlnode, test_only);
140 }
141
142 void yf::Zoom::process(mp::Package &package) const
143 {
144     m_p->process(package);
145 }
146
147
148 // define Implementation stuff
149
150 yf::Zoom::Backend::Backend(SearchablePtr ptr) : sptr(ptr)
151 {
152     m_connection = ZOOM_connection_create(0);
153     m_resultset = 0;
154     xsp = 0;
155 }
156
157 yf::Zoom::Backend::~Backend()
158 {
159     if (xsp)
160         xsltFreeStylesheet(xsp);
161     ZOOM_connection_destroy(m_connection);
162     ZOOM_resultset_destroy(m_resultset);
163 }
164
165 void yf::Zoom::Backend::connect(std::string zurl,
166                                 int *error, const char **addinfo)
167 {
168     ZOOM_connection_connect(m_connection, zurl.c_str(), 0);
169     *error = ZOOM_connection_error(m_connection, 0, addinfo);
170 }
171
172 void yf::Zoom::Backend::search_pqf(const char *pqf, Odr_int *hits,
173                                    int *error, const char **addinfo)
174 {
175     m_resultset = ZOOM_connection_search_pqf(m_connection, pqf);
176     *error = ZOOM_connection_error(m_connection, 0, addinfo);
177     if (*error == 0)
178         *hits = ZOOM_resultset_size(m_resultset);
179     else
180         *hits = 0;
181 }
182
183 void yf::Zoom::Backend::present(Odr_int start, Odr_int number,
184                                 ZOOM_record *recs,
185                                 int *error, const char **addinfo)
186 {
187     ZOOM_resultset_records(m_resultset, recs, start, number);
188     *error = ZOOM_connection_error(m_connection, 0, addinfo);
189 }
190
191 void yf::Zoom::Backend::set_option(const char *name, const char *value)
192 {
193     ZOOM_connection_option_set(m_connection, name, value);
194     if (m_resultset)
195         ZOOM_resultset_option_set(m_resultset, name, value);
196 }
197
198 int yf::Zoom::Backend::get_error(const char **addinfo)
199 {
200     return ZOOM_connection_error(m_connection, 0, addinfo);
201 }
202
203 yf::Zoom::Searchable::Searchable()
204 {
205     piggyback = true;
206     use_turbomarc = true;
207     ccl_bibset = ccl_qual_mk();
208 }
209
210 yf::Zoom::Searchable::~Searchable()
211 {
212     ccl_qual_rm(&ccl_bibset);
213 }
214
215 yf::Zoom::Frontend::Frontend(Impl *impl) : 
216     m_p(impl), m_is_virtual(false), m_in_use(true)
217 {
218 }
219
220 yf::Zoom::Frontend::~Frontend()
221 {
222 }
223
224 yf::Zoom::FrontendPtr yf::Zoom::Impl::get_frontend(mp::Package &package)
225 {
226     boost::mutex::scoped_lock lock(m_mutex);
227
228     std::map<mp::Session,yf::Zoom::FrontendPtr>::iterator it;
229     
230     while(true)
231     {
232         it = m_clients.find(package.session());
233         if (it == m_clients.end())
234             break;
235         
236         if (!it->second->m_in_use)
237         {
238             it->second->m_in_use = true;
239             return it->second;
240         }
241         m_cond_session_ready.wait(lock);
242     }
243     FrontendPtr f(new Frontend(this));
244     m_clients[package.session()] = f;
245     f->m_in_use = true;
246     return f;
247 }
248
249 void yf::Zoom::Impl::release_frontend(mp::Package &package)
250 {
251     boost::mutex::scoped_lock lock(m_mutex);
252     std::map<mp::Session,yf::Zoom::FrontendPtr>::iterator it;
253     
254     it = m_clients.find(package.session());
255     if (it != m_clients.end())
256     {
257         if (package.session().is_closed())
258         {
259             m_clients.erase(it);
260         }
261         else
262         {
263             it->second->m_in_use = false;
264         }
265         m_cond_session_ready.notify_all();
266     }
267 }
268
269 yf::Zoom::Impl::Impl()
270 {
271 }
272
273 yf::Zoom::Impl::~Impl()
274
275 }
276
277 void yf::Zoom::Impl::parse_torus(const xmlNode *ptr1)
278 {
279     if (!ptr1)
280         return ;
281     for (ptr1 = ptr1->children; ptr1; ptr1 = ptr1->next)
282     {
283         if (ptr1->type != XML_ELEMENT_NODE)
284             continue;
285         if (!strcmp((const char *) ptr1->name, "record"))
286         {
287             const xmlNode *ptr2 = ptr1;
288             for (ptr2 = ptr2->children; ptr2; ptr2 = ptr2->next)
289             {
290                 if (ptr2->type != XML_ELEMENT_NODE)
291                     continue;
292                 if (!strcmp((const char *) ptr2->name, "layer"))
293                 {
294                     Zoom::SearchablePtr s(new Searchable);
295
296                     const xmlNode *ptr3 = ptr2;
297                     for (ptr3 = ptr3->children; ptr3; ptr3 = ptr3->next)
298                     {
299                         if (ptr3->type != XML_ELEMENT_NODE)
300                             continue;
301                         if (!strcmp((const char *) ptr3->name,
302                                     "authentication"))
303                         {
304                             s->authentication = mp::xml::get_text(ptr3);
305                         }
306                         else if (!strcmp((const char *) ptr3->name, "id"))
307                         {
308                             s->database = mp::xml::get_text(ptr3);
309                         }
310                         else if (!strcmp((const char *) ptr3->name, "zurl"))
311                         {
312                             s->target = mp::xml::get_text(ptr3);
313                         }
314                         else if (!strcmp((const char *) ptr3->name, "sru"))
315                         {
316                             s->sru = mp::xml::get_text(ptr3);
317                         }
318                         else if (!strcmp((const char *) ptr3->name,
319                                          "queryEncoding"))
320                         {
321                             s->query_encoding = mp::xml::get_text(ptr3);
322                         }
323                         else if (!strcmp((const char *) ptr3->name,
324                                          "piggyback"))
325                         {
326                             s->piggyback = mp::xml::get_bool(ptr3, true);
327                         }
328                         else if (!strcmp((const char *) ptr3->name,
329                                          "requestSyntax"))
330                         {
331                             s->request_syntax = mp::xml::get_text(ptr3);
332                         }
333                         else if (!strcmp((const char *) ptr3->name,
334                                          "elementSet"))
335                         {
336                             s->element_set = mp::xml::get_text(ptr3);
337                         }
338                         else if (!strcmp((const char *) ptr3->name,
339                                          "recordEncoding"))
340                         {
341                             s->record_encoding = mp::xml::get_text(ptr3);
342                         }
343                         else if (!strcmp((const char *) ptr3->name,
344                                          "transform"))
345                         {
346                             s->transform_xsl_fname = mp::xml::get_text(ptr3);
347                         }
348                         else if (!strcmp((const char *) ptr3->name,
349                                          "useTurboMarc"))
350                         {
351                             ; // useTurboMarc is ignored
352                         }
353                         else if (!strncmp((const char *) ptr3->name,
354                                           "cclmap_", 7))
355                         {
356                             std::string value = mp::xml::get_text(ptr3);
357                             ccl_qual_fitem(s->ccl_bibset, value.c_str(),
358                                            (const char *) ptr3->name + 7);
359                         }
360                     }
361                     if (s->database.length() && s->target.length())
362                     {
363                         yaz_log(YLOG_LOG, "add db=%s target=%s turbomarc=%s", 
364                                 s->database.c_str(), s->target.c_str(),
365                                 s->use_turbomarc ? "1" : "0");
366                         m_searchables.push_back(s);
367                     }
368                 }
369             }
370         }
371     }
372 }
373
374 void yf::Zoom::Impl::configure(const xmlNode *ptr, bool test_only)
375 {
376     for (ptr = ptr->children; ptr; ptr = ptr->next)
377     {
378         if (ptr->type != XML_ELEMENT_NODE)
379             continue;
380         if (!strcmp((const char *) ptr->name, "records"))
381         {
382             parse_torus(ptr);
383         }
384         else if (!strcmp((const char *) ptr->name, "torus"))
385         {
386             std::string url;
387             const struct _xmlAttr *attr;
388             for (attr = ptr->properties; attr; attr = attr->next)
389             {
390                 if (!strcmp((const char *) attr->name, "url"))
391                     url = mp::xml::get_text(attr->children);
392                 else
393                     throw mp::filter::FilterException(
394                         "Bad attribute " + std::string((const char *)
395                                                        attr->name));
396             }
397             torus.read_searchables(url);
398             xmlDoc *doc = torus.get_doc();
399             if (doc)
400             {
401                 xmlNode *ptr = xmlDocGetRootElement(doc);
402                 parse_torus(ptr);
403             }
404         }
405         else
406         {
407             throw mp::filter::FilterException
408                 ("Bad element " 
409                  + std::string((const char *) ptr->name)
410                  + " in zoom filter");
411         }
412     }
413 }
414
415 yf::Zoom::BackendPtr yf::Zoom::Frontend::get_backend_from_databases(
416     std::string &database, int *error, const char **addinfo)
417 {
418     std::list<BackendPtr>::const_iterator map_it;
419     if (m_backend && m_backend->m_frontend_database == database)
420         return m_backend;
421
422     std::list<Zoom::SearchablePtr>::iterator map_s =
423         m_p->m_searchables.begin();
424
425     std::string c_db = mp::util::database_name_normalize(database);
426
427     while (map_s != m_p->m_searchables.end())
428     {
429         if (c_db.compare((*map_s)->database) == 0)
430             break;
431         map_s++;
432     }
433     if (map_s == m_p->m_searchables.end())
434     {
435         *error = YAZ_BIB1_DATABASE_DOES_NOT_EXIST;
436         *addinfo = database.c_str();
437         BackendPtr b;
438         return b;
439     }
440
441     xsltStylesheetPtr xsp = 0;
442     if ((*map_s)->transform_xsl_fname.length())
443     {
444         xmlDoc *xsp_doc = xmlParseFile((*map_s)->transform_xsl_fname.c_str());
445         if (!xsp_doc)
446         {
447             *error = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
448             *addinfo = "xmlParseFile failed";
449             BackendPtr b;
450             return b;
451         }
452         xsp = xsltParseStylesheetDoc(xsp_doc);
453         if (!xsp)
454         {
455             *error = YAZ_BIB1_DATABASE_DOES_NOT_EXIST;
456             *addinfo = "xsltParseStylesheetDoc failed";
457             BackendPtr b;
458             xmlFreeDoc(xsp_doc);
459             return b;
460         }
461     }
462
463     SearchablePtr sptr = *map_s;
464
465     m_backend.reset();
466
467     BackendPtr b(new Backend(sptr));
468
469     b->xsp = xsp;
470     b->m_frontend_database = database;
471
472     if (sptr->query_encoding.length())
473         b->set_option("rpnCharset", sptr->query_encoding.c_str());
474
475     if (sptr->authentication.length())
476         b->set_option("user", sptr->authentication.c_str());
477
478     std::string url;
479     if (sptr->sru.length())
480     {
481         url = "http://" + sptr->target;
482         b->set_option("sru", sptr->sru.c_str());
483     }
484     else
485         url = sptr->target;
486
487     b->connect(url, error, addinfo);
488     if (*error == 0)
489     {
490         m_backend = b;
491     }
492     return b;
493 }
494
495 Z_Records *yf::Zoom::Frontend::get_records(Odr_int start,
496                                            Odr_int number_to_present,
497                                            int *error,
498                                            const char **addinfo,
499                                            Odr_int *number_of_records_returned,
500                                            ODR odr,
501                                            BackendPtr b,
502                                            Odr_oid *preferredRecordSyntax,
503                                            const char *element_set_name)
504 {
505     *number_of_records_returned = 0;
506     Z_Records *records = 0;
507     bool enable_pz2_transform = false;
508
509     if (start < 0 || number_to_present <= 0)
510         return records;
511     
512     if (number_to_present > 10000)
513         number_to_present = 10000;
514     
515     ZOOM_record *recs = (ZOOM_record *)
516         odr_malloc(odr, number_to_present * sizeof(*recs));
517
518     char oid_name_str[OID_STR_MAX];
519     const char *syntax_name = 0;
520
521     if (preferredRecordSyntax)
522     {
523         if (!oid_oidcmp(preferredRecordSyntax, yaz_oid_recsyn_xml)
524             && !strcmp(element_set_name, "pz2"))
525         {
526             if (b->sptr->request_syntax.length())
527             {
528                 syntax_name = b->sptr->request_syntax.c_str();
529                 enable_pz2_transform = true;
530             }
531         }
532         else
533         {
534             syntax_name =
535                 yaz_oid_to_string_buf(preferredRecordSyntax, 0, oid_name_str);
536         }
537     }
538
539     b->set_option("preferredRecordSyntax", syntax_name);
540
541     if (enable_pz2_transform)
542     {
543         element_set_name = "F";
544         if (b->sptr->element_set.length())
545             element_set_name = b->sptr->element_set.c_str();
546     }
547
548     b->set_option("elementSetName", element_set_name);
549
550     b->present(start, number_to_present, recs, error, addinfo);
551
552     Odr_int i = 0;
553     if (!*error)
554     {
555         for (i = 0; i < number_to_present; i++)
556             if (!recs[i])
557                 break;
558     }
559     if (i > 0)
560     {  // only return records if no error and at least one record
561         char *odr_database = odr_strdup(odr,
562                                         b->m_frontend_database.c_str());
563         Z_NamePlusRecordList *npl = (Z_NamePlusRecordList *)
564             odr_malloc(odr, sizeof(*npl));
565         *number_of_records_returned = i;
566         npl->num_records = i;
567         npl->records = (Z_NamePlusRecord **)
568             odr_malloc(odr, i * sizeof(*npl->records));
569         for (i = 0; i < number_to_present; i++)
570         {
571             Z_NamePlusRecord *npr = 0;
572             const char *addinfo;
573             int sur_error = ZOOM_record_error(recs[i], 0 /* msg */,
574                                               &addinfo, 0 /* diagset */);
575                 
576             if (sur_error)
577             {
578                 npr = zget_surrogateDiagRec(odr, odr_database, sur_error,
579                                             addinfo);
580             }
581             else if (enable_pz2_transform)
582             {
583                 char rec_type_str[100];
584
585                 strcpy(rec_type_str, b->sptr->use_turbomarc ?
586                        "txml" : "xml");
587                 
588                 // prevent buffer overflow ...
589                 if (b->sptr->record_encoding.length() > 0 &&
590                     b->sptr->record_encoding.length() < 
591                     (sizeof(rec_type_str)-20))
592                 {
593                     strcat(rec_type_str, "; charset=");
594                     strcat(rec_type_str, b->sptr->record_encoding.c_str());
595                 }
596                 
597                 int rec_len;
598                 const char *rec_buf = ZOOM_record_get(recs[i], rec_type_str,
599                                                       &rec_len);
600                 if (rec_buf && b->xsp)
601                 {
602                     xmlDoc *rec_doc = xmlParseMemory(rec_buf, rec_len);
603                     if (rec_doc)
604                     { 
605                         xmlDoc *rec_res;
606                         rec_res = xsltApplyStylesheet(b->xsp, rec_doc, 0);
607
608                         if (rec_res)
609                             xsltSaveResultToString((xmlChar **) &rec_buf, &rec_len,
610                                                    rec_res, b->xsp);
611                     }
612                 }
613
614                 if (rec_buf)
615                 {
616                     npr = (Z_NamePlusRecord *) odr_malloc(odr, sizeof(*npr));
617                     npr->databaseName = odr_database;
618                     npr->which = Z_NamePlusRecord_databaseRecord;
619                     npr->u.databaseRecord =
620                         z_ext_record_xml(odr, rec_buf, rec_len);
621                 }
622                 else
623                 {
624                     npr = zget_surrogateDiagRec(
625                         odr, odr_database, 
626                         YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS,
627                         rec_type_str);
628                 }
629             }
630             else
631             {
632                 Z_External *ext =
633                     (Z_External *) ZOOM_record_get(recs[i], "ext", 0);
634                 if (ext)
635                 {
636                     npr = (Z_NamePlusRecord *) odr_malloc(odr, sizeof(*npr));
637                     npr->databaseName = odr_database;
638                     npr->which = Z_NamePlusRecord_databaseRecord;
639                     npr->u.databaseRecord = ext;
640                 }
641                 else
642                 {
643                     npr = zget_surrogateDiagRec(
644                         odr, odr_database, 
645                         YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS,
646                         "ZOOM_record, type ext");
647                 }
648             }
649             npl->records[i] = npr;
650         }
651         records = (Z_Records*) odr_malloc(odr, sizeof(*records));
652         records->which = Z_Records_DBOSD;
653         records->u.databaseOrSurDiagnostics = npl;
654     }
655     return records;
656 }
657     
658
659 void yf::Zoom::Frontend::handle_search(mp::Package &package)
660 {
661     Z_GDU *gdu = package.request().get();
662     Z_APDU *apdu_req = gdu->u.z3950;
663     Z_APDU *apdu_res = 0;
664     mp::odr odr;
665     Z_SearchRequest *sr = apdu_req->u.searchRequest;
666     if (sr->num_databaseNames != 1)
667     {
668         apdu_res = odr.create_searchResponse(
669             apdu_req, YAZ_BIB1_TOO_MANY_DATABASES_SPECIFIED, 0);
670         package.response() = apdu_res;
671         return;
672     }
673
674     int error = 0;
675     const char *addinfo = 0;
676     std::string db(sr->databaseNames[0]);
677     BackendPtr b = get_backend_from_databases(db, &error, &addinfo);
678     if (error)
679     {
680         apdu_res = 
681             odr.create_searchResponse(
682                 apdu_req, error, addinfo);
683         package.response() = apdu_res;
684         return;
685     }
686
687     b->set_option("setname", "default");
688
689     Odr_int hits = 0;
690     Z_Query *query = sr->query;
691     WRBUF ccl_wrbuf = 0;
692     WRBUF pqf_wrbuf = 0;
693
694     if (query->which == Z_Query_type_1 || query->which == Z_Query_type_101)
695     {
696         // RPN
697         pqf_wrbuf = wrbuf_alloc();
698         yaz_rpnquery_to_wrbuf(pqf_wrbuf, query->u.type_1);
699     }
700     else if (query->which == Z_Query_type_2)
701     {
702         // CCL
703         ccl_wrbuf = wrbuf_alloc();
704         wrbuf_write(ccl_wrbuf, (const char *) query->u.type_2->buf,
705                     query->u.type_2->len);
706     }
707     else if (query->which == Z_Query_type_104 &&
708              query->u.type_104->which == Z_External_CQL)
709     {
710         // CQL
711         const char *cql = query->u.type_104->u.cql;
712         CQL_parser cp = cql_parser_create();
713         int r = cql_parser_string(cp, cql);
714         if (r)
715         {
716             cql_parser_destroy(cp);
717             apdu_res = 
718                 odr.create_searchResponse(apdu_req, 
719                                           YAZ_BIB1_MALFORMED_QUERY,
720                                           "CQL syntax error");
721             package.response() = apdu_res;
722             return;
723         }
724         struct cql_node *cn = cql_parser_result(cp);
725         char ccl_buf[1024];
726
727         r = cql_to_ccl_buf(cn, ccl_buf, sizeof(ccl_buf));
728         yaz_log(YLOG_LOG, "cql_to_ccl_buf returned %d", r);
729         if (r == 0)
730         {
731             ccl_wrbuf = wrbuf_alloc();
732             wrbuf_puts(ccl_wrbuf, ccl_buf);
733         }
734         cql_parser_destroy(cp);
735         if (r)
736         {
737             apdu_res = 
738                 odr.create_searchResponse(apdu_req, 
739                                           YAZ_BIB1_MALFORMED_QUERY,
740                                           "CQL to CCL conversion error");
741             package.response() = apdu_res;
742             return;
743         }
744     }
745     else
746     {
747         apdu_res = 
748             odr.create_searchResponse(apdu_req, YAZ_BIB1_QUERY_TYPE_UNSUPP, 0);
749         package.response() = apdu_res;
750         return;
751     }
752
753     if (ccl_wrbuf)
754     {
755         // CCL to PQF
756         assert(pqf_wrbuf == 0);
757         int cerror, cpos;
758         struct ccl_rpn_node *cn;
759         cn = ccl_find_str(b->sptr->ccl_bibset, wrbuf_cstr(ccl_wrbuf),
760                           &cerror, &cpos);
761         wrbuf_destroy(ccl_wrbuf);
762         if (!cn)
763         {
764             char *addinfo = odr_strdup(odr, ccl_err_msg(cerror));
765
766             apdu_res = 
767                 odr.create_searchResponse(apdu_req, 
768                                           YAZ_BIB1_MALFORMED_QUERY,
769                                           addinfo);
770             package.response() = apdu_res;
771             return;
772         }
773         pqf_wrbuf = wrbuf_alloc();
774         ccl_pquery(pqf_wrbuf, cn);
775         ccl_rpn_delete(cn);
776     }
777     
778     assert(pqf_wrbuf);
779     b->search_pqf(wrbuf_cstr(pqf_wrbuf), &hits, &error, &addinfo);
780     
781     wrbuf_destroy(pqf_wrbuf);
782     
783     const char *element_set_name = 0;
784     Odr_int number_to_present = 0;
785     if (!error)
786         mp::util::piggyback_sr(sr, hits, number_to_present, &element_set_name);
787     
788     Odr_int number_of_records_returned = 0;
789     Z_Records *records = get_records(
790         0, number_to_present, &error, &addinfo,
791         &number_of_records_returned, odr, b, sr->preferredRecordSyntax,
792         element_set_name);
793     apdu_res = odr.create_searchResponse(apdu_req, error, addinfo);
794     if (records)
795     {
796         apdu_res->u.searchResponse->records = records;
797         apdu_res->u.searchResponse->numberOfRecordsReturned =
798             odr_intdup(odr, number_of_records_returned);
799     }
800     apdu_res->u.searchResponse->resultCount = odr_intdup(odr, hits);
801     package.response() = apdu_res;
802 }
803
804 void yf::Zoom::Frontend::handle_present(mp::Package &package)
805 {
806     Z_GDU *gdu = package.request().get();
807     Z_APDU *apdu_req = gdu->u.z3950;
808     Z_APDU *apdu_res = 0;
809     Z_PresentRequest *pr = apdu_req->u.presentRequest;
810
811     mp::odr odr;
812     if (!m_backend)
813     {
814         package.response() = odr.create_presentResponse(
815             apdu_req, YAZ_BIB1_SPECIFIED_RESULT_SET_DOES_NOT_EXIST, 0);
816         return;
817     }
818     const char *element_set_name = 0;
819     Z_RecordComposition *comp = pr->recordComposition;
820     if (comp && comp->which != Z_RecordComp_simple)
821     {
822         package.response() = odr.create_presentResponse(
823             apdu_req, 
824             YAZ_BIB1_PRESENT_COMP_SPEC_PARAMETER_UNSUPP, 0);
825         return;
826     }
827     if (comp && comp->u.simple->which == Z_ElementSetNames_generic)
828         element_set_name = comp->u.simple->u.generic;
829     Odr_int number_of_records_returned = 0;
830     int error = 0;
831     const char *addinfo = 0;
832     Z_Records *records = get_records(
833         *pr->resultSetStartPoint - 1, *pr->numberOfRecordsRequested,
834         &error, &addinfo, &number_of_records_returned, odr, m_backend,
835         pr->preferredRecordSyntax, element_set_name);
836
837     apdu_res = odr.create_presentResponse(apdu_req, error, addinfo);
838     if (records)
839     {
840         apdu_res->u.presentResponse->records = records;
841         apdu_res->u.presentResponse->numberOfRecordsReturned =
842             odr_intdup(odr, number_of_records_returned);
843     }
844     package.response() = apdu_res;
845 }
846
847 void yf::Zoom::Frontend::handle_package(mp::Package &package)
848 {
849     Z_GDU *gdu = package.request().get();
850     if (!gdu)
851         ;
852     else if (gdu->which == Z_GDU_Z3950)
853     {
854         Z_APDU *apdu_req = gdu->u.z3950;
855         if (apdu_req->which == Z_APDU_initRequest)
856         {
857             mp::odr odr;
858             package.response() = odr.create_close(
859                 apdu_req,
860                 Z_Close_protocolError,
861                 "double init");
862         }
863         else if (apdu_req->which == Z_APDU_searchRequest)
864         {
865             handle_search(package);
866         }
867         else if (apdu_req->which == Z_APDU_presentRequest)
868         {
869             handle_present(package);
870         }
871         else
872         {
873             mp::odr odr;
874             package.response() = odr.create_close(
875                 apdu_req,
876                 Z_Close_protocolError,
877                 "zoom filter cannot handle this APDU");
878             package.session().close();
879         }
880     }
881     else
882     {
883         package.session().close();
884     }
885 }
886
887 void yf::Zoom::Impl::process(mp::Package &package)
888 {
889     FrontendPtr f = get_frontend(package);
890     Z_GDU *gdu = package.request().get();
891
892     if (f->m_is_virtual)
893     {
894         f->handle_package(package);
895     }
896     else if (gdu && gdu->which == Z_GDU_Z3950 && gdu->u.z3950->which ==
897              Z_APDU_initRequest)
898     {
899         Z_InitRequest *req = gdu->u.z3950->u.initRequest;
900         f->m_init_gdu = gdu;
901         
902         mp::odr odr;
903         Z_APDU *apdu = odr.create_initResponse(gdu->u.z3950, 0, 0);
904         Z_InitResponse *resp = apdu->u.initResponse;
905         
906         int i;
907         static const int masks[] = {
908             Z_Options_search,
909             Z_Options_present,
910             -1 
911         };
912         for (i = 0; masks[i] != -1; i++)
913             if (ODR_MASK_GET(req->options, masks[i]))
914                 ODR_MASK_SET(resp->options, masks[i]);
915         
916         static const int versions[] = {
917             Z_ProtocolVersion_1,
918             Z_ProtocolVersion_2,
919             Z_ProtocolVersion_3,
920             -1
921         };
922         for (i = 0; versions[i] != -1; i++)
923             if (ODR_MASK_GET(req->protocolVersion, versions[i]))
924                 ODR_MASK_SET(resp->protocolVersion, versions[i]);
925             else
926                 break;
927         
928         *resp->preferredMessageSize = *req->preferredMessageSize;
929         *resp->maximumRecordSize = *req->maximumRecordSize;
930         
931         package.response() = apdu;
932         f->m_is_virtual = true;
933     }
934     else
935         package.move();
936
937     release_frontend(package);
938 }
939
940
941 static mp::filter::Base* filter_creator()
942 {
943     return new mp::filter::Zoom;
944 }
945
946 extern "C" {
947     struct metaproxy_1_filter_struct metaproxy_1_filter_zoom = {
948         0,
949         "zoom",
950         filter_creator
951     };
952 }
953
954
955 /*
956  * Local variables:
957  * c-basic-offset: 4
958  * c-file-style: "Stroustrup"
959  * indent-tabs-mode: nil
960  * End:
961  * vim: shiftwidth=4 tabstop=8 expandtab
962  */
963