zoom: xsldir setting (attribute for torus 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 <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                     if (s->database.length() && s->target.length())
381                     {
382                         yaz_log(YLOG_LOG, "add db=%s target=%s", 
383                                 s->database.c_str(), s->target.c_str());
384                     }
385                     return s;
386                 }
387             }
388         }
389     }
390     return notfound;
391 }
392
393 void yf::Zoom::Impl::configure(const xmlNode *ptr, bool test_only)
394 {
395     for (ptr = ptr->children; ptr; ptr = ptr->next)
396     {
397         if (ptr->type != XML_ELEMENT_NODE)
398             continue;
399         else if (!strcmp((const char *) ptr->name, "torus"))
400         {
401             const struct _xmlAttr *attr;
402             for (attr = ptr->properties; attr; attr = attr->next)
403             {
404                 if (!strcmp((const char *) attr->name, "url"))
405                     torus_url = mp::xml::get_text(attr->children);
406                 else if (!strcmp((const char *) attr->name, "xsldir"))
407                     xsldir = mp::xml::get_text(attr->children);
408                 else
409                     throw mp::filter::FilterException(
410                         "Bad attribute " + std::string((const char *)
411                                                        attr->name));
412             }
413         }
414         else if (!strcmp((const char *) ptr->name, "records"))
415         {
416             yaz_log(YLOG_WARN, "records ignored!");
417         }
418         else
419         {
420             throw mp::filter::FilterException
421                 ("Bad element " 
422                  + std::string((const char *) ptr->name)
423                  + " in zoom filter");
424         }
425     }
426 }
427
428 yf::Zoom::BackendPtr yf::Zoom::Frontend::get_backend_from_databases(
429     std::string &database, int *error, const char **addinfo)
430 {
431     std::list<BackendPtr>::const_iterator map_it;
432     if (m_backend && m_backend->m_frontend_database == database)
433         return m_backend;
434
435     xmlDoc *doc = mp::get_searchable(m_p->torus_url, database);
436     if (!doc)
437     {
438         *error = YAZ_BIB1_DATABASE_DOES_NOT_EXIST;
439         *addinfo = database.c_str();
440         BackendPtr b;
441         return b;
442     }
443     SearchablePtr sptr = m_p->parse_torus(xmlDocGetRootElement(doc));
444     xmlFreeDoc(doc);
445     if (!sptr)
446     {
447         *error = YAZ_BIB1_DATABASE_DOES_NOT_EXIST;
448         *addinfo = database.c_str();
449         BackendPtr b;
450         return b;
451     }
452         
453     xsltStylesheetPtr xsp = 0;
454     if (sptr->transform_xsl_fname.length())
455     {
456         std::string fname;
457
458         if (m_p->xsldir.length()) 
459             fname = m_p->xsldir + "/" + sptr->transform_xsl_fname;
460         else
461             fname = sptr->transform_xsl_fname;
462         xmlDoc *xsp_doc = xmlParseFile(fname.c_str());
463         if (!xsp_doc)
464         {
465             *error = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
466             *addinfo = "xmlParseFile failed";
467             BackendPtr b;
468             return b;
469         }
470         xsp = xsltParseStylesheetDoc(xsp_doc);
471         if (!xsp)
472         {
473             *error = YAZ_BIB1_DATABASE_DOES_NOT_EXIST;
474             *addinfo = "xsltParseStylesheetDoc failed";
475             BackendPtr b;
476             xmlFreeDoc(xsp_doc);
477             return b;
478         }
479     }
480
481     m_backend.reset();
482
483     BackendPtr b(new Backend(sptr));
484
485     std::string cf_parm;
486     b->xsp = xsp;
487     b->m_frontend_database = database;
488     std::string authentication = sptr->authentication;
489
490     if (sptr->query_encoding.length())
491         b->set_option("rpnCharset", sptr->query_encoding.c_str());
492
493     if (sptr->cfAuth.length())
494     {
495         b->set_option("user", sptr->cfAuth.c_str());
496         if (authentication.length())
497         {
498             size_t found = authentication.find('/');
499             if (found != std::string::npos)
500             {
501                 cf_parm += "user=" + mp::util::uri_encode(authentication.substr(0, found))
502                     + "&password=" + mp::util::uri_encode(authentication.substr(found+1));
503             }
504             else
505                 cf_parm += "user=" + mp::util::uri_encode(authentication);
506         }
507     }
508     else if (authentication.length())
509         b->set_option("user", authentication.c_str());
510
511     if (sptr->cfProxy.length())
512     {
513         if (cf_parm.length())
514             cf_parm += "&";
515         cf_parm += "proxy=" + mp::util::uri_encode(sptr->cfProxy);
516     }
517     if (sptr->cfSubDb.length())
518     {
519         if (cf_parm.length())
520             cf_parm += "&";
521         cf_parm += "subdatabase=" + mp::util::uri_encode(sptr->cfSubDb);
522     }
523
524     std::string url;
525     if (sptr->sru.length())
526     {
527         url = "http://" + sptr->target;
528         b->set_option("sru", sptr->sru.c_str());
529     }
530     else
531     {
532         url = sptr->target;
533     }
534     if (cf_parm.length())
535     {
536         url += "," + cf_parm;
537     }
538     b->connect(url, error, addinfo);
539     if (*error == 0)
540     {
541         m_backend = b;
542     }
543     return b;
544 }
545
546 Z_Records *yf::Zoom::Frontend::get_records(Odr_int start,
547                                            Odr_int number_to_present,
548                                            int *error,
549                                            const char **addinfo,
550                                            Odr_int *number_of_records_returned,
551                                            ODR odr,
552                                            BackendPtr b,
553                                            Odr_oid *preferredRecordSyntax,
554                                            const char *element_set_name)
555 {
556     *number_of_records_returned = 0;
557     Z_Records *records = 0;
558     bool enable_pz2_transform = false;
559
560     if (start < 0 || number_to_present <= 0)
561         return records;
562     
563     if (number_to_present > 10000)
564         number_to_present = 10000;
565     
566     ZOOM_record *recs = (ZOOM_record *)
567         odr_malloc(odr, number_to_present * sizeof(*recs));
568
569     char oid_name_str[OID_STR_MAX];
570     const char *syntax_name = 0;
571
572     if (preferredRecordSyntax)
573     {
574         if (!oid_oidcmp(preferredRecordSyntax, yaz_oid_recsyn_xml)
575             && element_set_name &&
576             !strcmp(element_set_name, "pz2"))
577         {
578             if (b->sptr->request_syntax.length())
579             {
580                 syntax_name = b->sptr->request_syntax.c_str();
581                 enable_pz2_transform = true;
582             }
583         }
584         else
585         {
586             syntax_name =
587                 yaz_oid_to_string_buf(preferredRecordSyntax, 0, oid_name_str);
588         }
589     }
590
591     b->set_option("preferredRecordSyntax", syntax_name);
592
593     if (enable_pz2_transform)
594     {
595         element_set_name = "F";
596         if (b->sptr->element_set.length())
597             element_set_name = b->sptr->element_set.c_str();
598     }
599
600     b->set_option("elementSetName", element_set_name);
601
602     b->present(start, number_to_present, recs, error, addinfo);
603
604     Odr_int i = 0;
605     if (!*error)
606     {
607         for (i = 0; i < number_to_present; i++)
608             if (!recs[i])
609                 break;
610     }
611     if (i > 0)
612     {  // only return records if no error and at least one record
613         char *odr_database = odr_strdup(odr,
614                                         b->m_frontend_database.c_str());
615         Z_NamePlusRecordList *npl = (Z_NamePlusRecordList *)
616             odr_malloc(odr, sizeof(*npl));
617         *number_of_records_returned = i;
618         npl->num_records = i;
619         npl->records = (Z_NamePlusRecord **)
620             odr_malloc(odr, i * sizeof(*npl->records));
621         for (i = 0; i < number_to_present; i++)
622         {
623             Z_NamePlusRecord *npr = 0;
624             const char *addinfo;
625             int sur_error = ZOOM_record_error(recs[i], 0 /* msg */,
626                                               &addinfo, 0 /* diagset */);
627                 
628             if (sur_error)
629             {
630                 npr = zget_surrogateDiagRec(odr, odr_database, sur_error,
631                                             addinfo);
632             }
633             else if (enable_pz2_transform)
634             {
635                 char rec_type_str[100];
636
637                 strcpy(rec_type_str, b->sptr->use_turbomarc ?
638                        "txml" : "xml");
639                 
640                 // prevent buffer overflow ...
641                 if (b->sptr->record_encoding.length() > 0 &&
642                     b->sptr->record_encoding.length() < 
643                     (sizeof(rec_type_str)-20))
644                 {
645                     strcat(rec_type_str, "; charset=");
646                     strcat(rec_type_str, b->sptr->record_encoding.c_str());
647                 }
648                 
649                 int rec_len;
650                 const char *rec_buf = ZOOM_record_get(recs[i], rec_type_str,
651                                                       &rec_len);
652                 if (rec_buf && b->xsp)
653                 {
654                     xmlDoc *rec_doc = xmlParseMemory(rec_buf, rec_len);
655                     if (rec_doc)
656                     { 
657                         xmlDoc *rec_res;
658                         rec_res = xsltApplyStylesheet(b->xsp, rec_doc, 0);
659
660                         if (rec_res)
661                             xsltSaveResultToString((xmlChar **) &rec_buf, &rec_len,
662                                                    rec_res, b->xsp);
663                     }
664                 }
665
666                 if (rec_buf)
667                 {
668                     npr = (Z_NamePlusRecord *) odr_malloc(odr, sizeof(*npr));
669                     npr->databaseName = odr_database;
670                     npr->which = Z_NamePlusRecord_databaseRecord;
671                     npr->u.databaseRecord =
672                         z_ext_record_xml(odr, rec_buf, rec_len);
673                 }
674                 else
675                 {
676                     npr = zget_surrogateDiagRec(
677                         odr, odr_database, 
678                         YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS,
679                         rec_type_str);
680                 }
681             }
682             else
683             {
684                 Z_External *ext =
685                     (Z_External *) ZOOM_record_get(recs[i], "ext", 0);
686                 if (ext)
687                 {
688                     npr = (Z_NamePlusRecord *) odr_malloc(odr, sizeof(*npr));
689                     npr->databaseName = odr_database;
690                     npr->which = Z_NamePlusRecord_databaseRecord;
691                     npr->u.databaseRecord = ext;
692                 }
693                 else
694                 {
695                     npr = zget_surrogateDiagRec(
696                         odr, odr_database, 
697                         YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS,
698                         "ZOOM_record, type ext");
699                 }
700             }
701             npl->records[i] = npr;
702         }
703         records = (Z_Records*) odr_malloc(odr, sizeof(*records));
704         records->which = Z_Records_DBOSD;
705         records->u.databaseOrSurDiagnostics = npl;
706     }
707     return records;
708 }
709     
710
711 void yf::Zoom::Frontend::handle_search(mp::Package &package)
712 {
713     Z_GDU *gdu = package.request().get();
714     Z_APDU *apdu_req = gdu->u.z3950;
715     Z_APDU *apdu_res = 0;
716     mp::odr odr;
717     Z_SearchRequest *sr = apdu_req->u.searchRequest;
718     if (sr->num_databaseNames != 1)
719     {
720         apdu_res = odr.create_searchResponse(
721             apdu_req, YAZ_BIB1_TOO_MANY_DATABASES_SPECIFIED, 0);
722         package.response() = apdu_res;
723         return;
724     }
725
726     int error = 0;
727     const char *addinfo = 0;
728     std::string db(sr->databaseNames[0]);
729     BackendPtr b = get_backend_from_databases(db, &error, &addinfo);
730     if (error)
731     {
732         apdu_res = 
733             odr.create_searchResponse(
734                 apdu_req, error, addinfo);
735         package.response() = apdu_res;
736         return;
737     }
738
739     b->set_option("setname", "default");
740
741     Odr_int hits = 0;
742     Z_Query *query = sr->query;
743     WRBUF ccl_wrbuf = 0;
744     WRBUF pqf_wrbuf = 0;
745
746     if (query->which == Z_Query_type_1 || query->which == Z_Query_type_101)
747     {
748         // RPN
749         pqf_wrbuf = wrbuf_alloc();
750         yaz_rpnquery_to_wrbuf(pqf_wrbuf, query->u.type_1);
751     }
752     else if (query->which == Z_Query_type_2)
753     {
754         // CCL
755         ccl_wrbuf = wrbuf_alloc();
756         wrbuf_write(ccl_wrbuf, (const char *) query->u.type_2->buf,
757                     query->u.type_2->len);
758     }
759     else if (query->which == Z_Query_type_104 &&
760              query->u.type_104->which == Z_External_CQL)
761     {
762         // CQL
763         const char *cql = query->u.type_104->u.cql;
764         CQL_parser cp = cql_parser_create();
765         int r = cql_parser_string(cp, cql);
766         if (r)
767         {
768             cql_parser_destroy(cp);
769             apdu_res = 
770                 odr.create_searchResponse(apdu_req, 
771                                           YAZ_BIB1_MALFORMED_QUERY,
772                                           "CQL syntax error");
773             package.response() = apdu_res;
774             return;
775         }
776         struct cql_node *cn = cql_parser_result(cp);
777         char ccl_buf[1024];
778
779         r = cql_to_ccl_buf(cn, ccl_buf, sizeof(ccl_buf));
780         yaz_log(YLOG_LOG, "cql_to_ccl_buf returned %d", r);
781         if (r == 0)
782         {
783             ccl_wrbuf = wrbuf_alloc();
784             wrbuf_puts(ccl_wrbuf, ccl_buf);
785         }
786         cql_parser_destroy(cp);
787         if (r)
788         {
789             apdu_res = 
790                 odr.create_searchResponse(apdu_req, 
791                                           YAZ_BIB1_MALFORMED_QUERY,
792                                           "CQL to CCL conversion error");
793             package.response() = apdu_res;
794             return;
795         }
796     }
797     else
798     {
799         apdu_res = 
800             odr.create_searchResponse(apdu_req, YAZ_BIB1_QUERY_TYPE_UNSUPP, 0);
801         package.response() = apdu_res;
802         return;
803     }
804
805     if (ccl_wrbuf)
806     {
807         // CCL to PQF
808         assert(pqf_wrbuf == 0);
809         int cerror, cpos;
810         struct ccl_rpn_node *cn;
811         cn = ccl_find_str(b->sptr->ccl_bibset, wrbuf_cstr(ccl_wrbuf),
812                           &cerror, &cpos);
813         wrbuf_destroy(ccl_wrbuf);
814         if (!cn)
815         {
816             char *addinfo = odr_strdup(odr, ccl_err_msg(cerror));
817
818             apdu_res = 
819                 odr.create_searchResponse(apdu_req, 
820                                           YAZ_BIB1_MALFORMED_QUERY,
821                                           addinfo);
822             package.response() = apdu_res;
823             return;
824         }
825         pqf_wrbuf = wrbuf_alloc();
826         ccl_pquery(pqf_wrbuf, cn);
827         ccl_rpn_delete(cn);
828     }
829     
830     assert(pqf_wrbuf);
831     b->search_pqf(wrbuf_cstr(pqf_wrbuf), &hits, &error, &addinfo);
832     
833     wrbuf_destroy(pqf_wrbuf);
834     
835     const char *element_set_name = 0;
836     Odr_int number_to_present = 0;
837     if (!error)
838         mp::util::piggyback_sr(sr, hits, number_to_present, &element_set_name);
839     
840     Odr_int number_of_records_returned = 0;
841     Z_Records *records = get_records(
842         0, number_to_present, &error, &addinfo,
843         &number_of_records_returned, odr, b, sr->preferredRecordSyntax,
844         element_set_name);
845     apdu_res = odr.create_searchResponse(apdu_req, error, addinfo);
846     if (records)
847     {
848         apdu_res->u.searchResponse->records = records;
849         apdu_res->u.searchResponse->numberOfRecordsReturned =
850             odr_intdup(odr, number_of_records_returned);
851     }
852     apdu_res->u.searchResponse->resultCount = odr_intdup(odr, hits);
853     package.response() = apdu_res;
854 }
855
856 void yf::Zoom::Frontend::handle_present(mp::Package &package)
857 {
858     Z_GDU *gdu = package.request().get();
859     Z_APDU *apdu_req = gdu->u.z3950;
860     Z_APDU *apdu_res = 0;
861     Z_PresentRequest *pr = apdu_req->u.presentRequest;
862
863     mp::odr odr;
864     if (!m_backend)
865     {
866         package.response() = odr.create_presentResponse(
867             apdu_req, YAZ_BIB1_SPECIFIED_RESULT_SET_DOES_NOT_EXIST, 0);
868         return;
869     }
870     const char *element_set_name = 0;
871     Z_RecordComposition *comp = pr->recordComposition;
872     if (comp && comp->which != Z_RecordComp_simple)
873     {
874         package.response() = odr.create_presentResponse(
875             apdu_req, 
876             YAZ_BIB1_PRESENT_COMP_SPEC_PARAMETER_UNSUPP, 0);
877         return;
878     }
879     if (comp && comp->u.simple->which == Z_ElementSetNames_generic)
880         element_set_name = comp->u.simple->u.generic;
881     Odr_int number_of_records_returned = 0;
882     int error = 0;
883     const char *addinfo = 0;
884     Z_Records *records = get_records(
885         *pr->resultSetStartPoint - 1, *pr->numberOfRecordsRequested,
886         &error, &addinfo, &number_of_records_returned, odr, m_backend,
887         pr->preferredRecordSyntax, element_set_name);
888
889     apdu_res = odr.create_presentResponse(apdu_req, error, addinfo);
890     if (records)
891     {
892         apdu_res->u.presentResponse->records = records;
893         apdu_res->u.presentResponse->numberOfRecordsReturned =
894             odr_intdup(odr, number_of_records_returned);
895     }
896     package.response() = apdu_res;
897 }
898
899 void yf::Zoom::Frontend::handle_package(mp::Package &package)
900 {
901     Z_GDU *gdu = package.request().get();
902     if (!gdu)
903         ;
904     else if (gdu->which == Z_GDU_Z3950)
905     {
906         Z_APDU *apdu_req = gdu->u.z3950;
907         if (apdu_req->which == Z_APDU_initRequest)
908         {
909             mp::odr odr;
910             package.response() = odr.create_close(
911                 apdu_req,
912                 Z_Close_protocolError,
913                 "double init");
914         }
915         else if (apdu_req->which == Z_APDU_searchRequest)
916         {
917             handle_search(package);
918         }
919         else if (apdu_req->which == Z_APDU_presentRequest)
920         {
921             handle_present(package);
922         }
923         else
924         {
925             mp::odr odr;
926             package.response() = odr.create_close(
927                 apdu_req,
928                 Z_Close_protocolError,
929                 "zoom filter cannot handle this APDU");
930             package.session().close();
931         }
932     }
933     else
934     {
935         package.session().close();
936     }
937 }
938
939 void yf::Zoom::Impl::process(mp::Package &package)
940 {
941     FrontendPtr f = get_frontend(package);
942     Z_GDU *gdu = package.request().get();
943
944     if (f->m_is_virtual)
945     {
946         f->handle_package(package);
947     }
948     else if (gdu && gdu->which == Z_GDU_Z3950 && gdu->u.z3950->which ==
949              Z_APDU_initRequest)
950     {
951         Z_InitRequest *req = gdu->u.z3950->u.initRequest;
952         f->m_init_gdu = gdu;
953         
954         mp::odr odr;
955         Z_APDU *apdu = odr.create_initResponse(gdu->u.z3950, 0, 0);
956         Z_InitResponse *resp = apdu->u.initResponse;
957         
958         int i;
959         static const int masks[] = {
960             Z_Options_search,
961             Z_Options_present,
962             -1 
963         };
964         for (i = 0; masks[i] != -1; i++)
965             if (ODR_MASK_GET(req->options, masks[i]))
966                 ODR_MASK_SET(resp->options, masks[i]);
967         
968         static const int versions[] = {
969             Z_ProtocolVersion_1,
970             Z_ProtocolVersion_2,
971             Z_ProtocolVersion_3,
972             -1
973         };
974         for (i = 0; versions[i] != -1; i++)
975             if (ODR_MASK_GET(req->protocolVersion, versions[i]))
976                 ODR_MASK_SET(resp->protocolVersion, versions[i]);
977             else
978                 break;
979         
980         *resp->preferredMessageSize = *req->preferredMessageSize;
981         *resp->maximumRecordSize = *req->maximumRecordSize;
982         
983         package.response() = apdu;
984         f->m_is_virtual = true;
985     }
986     else
987         package.move();
988
989     release_frontend(package);
990 }
991
992
993 static mp::filter::Base* filter_creator()
994 {
995     return new mp::filter::Zoom;
996 }
997
998 extern "C" {
999     struct metaproxy_1_filter_struct metaproxy_1_filter_zoom = {
1000         0,
1001         "zoom",
1002         filter_creator
1003     };
1004 }
1005
1006
1007 /*
1008  * Local variables:
1009  * c-basic-offset: 4
1010  * c-file-style: "Stroustrup"
1011  * indent-tabs-mode: nil
1012  * End:
1013  * vim: shiftwidth=4 tabstop=8 expandtab
1014  */
1015