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