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