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