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