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