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