zoom: separate Torus URL for content things
[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
21 #include <stdlib.h>
22 #include <sys/types.h>
23 #include "filter_zoom.hpp"
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
35 #include <yaz/yaz-version.h>
36 #include <yaz/tpath.h>
37 #include <yaz/srw.h>
38 #include <yaz/ccl_xml.h>
39 #include <yaz/ccl.h>
40 #include <yaz/rpn2cql.h>
41 #include <yaz/rpn2solr.h>
42 #include <yaz/pquery.h>
43 #include <yaz/cql.h>
44 #include <yaz/oid_db.h>
45 #include <yaz/diagbib1.h>
46 #include <yaz/log.h>
47 #include <yaz/zgdu.h>
48 #include <yaz/querytowrbuf.h>
49 #include <yaz/sortspec.h>
50 #include <yaz/tokenizer.h>
51 #include <yaz/zoom.h>
52
53 namespace mp = metaproxy_1;
54 namespace yf = mp::filter;
55
56 namespace metaproxy_1 {
57     namespace filter {
58         class Zoom::Searchable : boost::noncopyable {
59           public:
60             std::string authentication;
61             std::string cfAuth;
62             std::string cfProxy;
63             std::string cfSubDB;
64             std::string udb;
65             std::string target;
66             std::string query_encoding;
67             std::string sru;
68             std::string sru_version;
69             std::string request_syntax;
70             std::string element_set;
71             std::string record_encoding;
72             std::string transform_xsl_fname;
73             std::string transform_xsl_content;
74             std::string urlRecipe;
75             std::string contentConnector;
76             std::string sortStrategy;
77             bool use_turbomarc;
78             bool piggyback;
79             CCL_bibset ccl_bibset;
80             std::map<std::string, std::string> sortmap;
81             Searchable(CCL_bibset base);
82             ~Searchable();
83         };
84         class Zoom::Backend : boost::noncopyable {
85             friend class Impl;
86             friend class Frontend;
87             std::string zurl;
88             WRBUF m_apdu_wrbuf;
89             ZOOM_connection m_connection;
90             ZOOM_resultset m_resultset;
91             std::string m_frontend_database;
92             SearchablePtr sptr;
93             xsltStylesheetPtr xsp;
94             std::string content_session_id;
95         public:
96             Backend(SearchablePtr sptr);
97             ~Backend();
98             void connect(std::string zurl, int *error, char **addinfo,
99                          ODR odr);
100             void search(ZOOM_query q, Odr_int *hits,
101                         int *error, char **addinfo, ODR odr);
102             void present(Odr_int start, Odr_int number, ZOOM_record *recs,
103                          int *error, char **addinfo, ODR odr);
104             void set_option(const char *name, const char *value);
105             void set_option(const char *name, std::string value);
106             const char *get_option(const char *name);
107             void get_zoom_error(int *error, char **addinfo, ODR odr);
108         };
109         class Zoom::Frontend : boost::noncopyable {
110             friend class Impl;
111             Impl *m_p;
112             bool m_is_virtual;
113             bool m_in_use;
114             yazpp_1::GDU m_init_gdu;
115             BackendPtr m_backend;
116             void handle_package(mp::Package &package);
117             void handle_search(mp::Package &package);
118             void handle_present(mp::Package &package);
119             BackendPtr get_backend_from_databases(mp::Package &package,
120                                                   std::string &database,
121                                                   int *error,
122                                                   char **addinfo,
123                                                   ODR odr);
124
125             bool create_content_session(mp::Package &package,
126                                         BackendPtr b,
127                                         int *error,
128                                         char **addinfo,
129                                         ODR odr,
130                                         std::string authentication,
131                                         std::string proxy);
132             
133             void prepare_elements(BackendPtr b,
134                                   Odr_oid *preferredRecordSyntax,
135                                   const char *element_set_name,
136                                   bool &enable_pz2_retrieval,
137                                   bool &enable_pz2_transform,
138                                   bool &assume_marc8_charset);
139
140             Z_Records *get_records(Package &package,
141                                    Odr_int start,
142                                    Odr_int number_to_present,
143                                    int *error,
144                                    char **addinfo,
145                                    Odr_int *number_of_records_returned,
146                                    ODR odr, BackendPtr b,
147                                    Odr_oid *preferredRecordSyntax,
148                                    const char *element_set_name);
149
150             void log_diagnostic(mp::Package &package,
151                                 int error, const char *addinfo);
152         public:
153             Frontend(Impl *impl);
154             ~Frontend();
155         };
156         class Zoom::Impl {
157             friend class Frontend;
158         public:
159             Impl();
160             ~Impl();
161             void process(metaproxy_1::Package & package);
162             void configure(const xmlNode * ptr, bool test_only,
163                            const char *path);
164         private:
165             void configure_local_records(const xmlNode * ptr, bool test_only);
166             FrontendPtr get_frontend(mp::Package &package);
167             void release_frontend(mp::Package &package);
168             SearchablePtr parse_torus_record(const xmlNode *ptr);
169             struct cql_node *convert_cql_fields(struct cql_node *cn, ODR odr);
170             std::map<mp::Session, FrontendPtr> m_clients;            
171             boost::mutex m_mutex;
172             boost::condition m_cond_session_ready;
173             std::string torus_searchable_url;
174             std::string torus_content_url;
175             std::string default_realm;
176             std::map<std::string,std::string> fieldmap;
177             std::string xsldir;
178             std::string file_path;
179             std::string content_proxy_server;
180             std::string content_tmp_file;
181             bool apdu_log;
182             CCL_bibset bibset;
183             std::string element_transform;
184             std::string element_raw;
185             std::string proxy;
186             std::map<std::string,SearchablePtr> s_map;
187         };
188     }
189 }
190
191 // define Pimpl wrapper forwarding to Impl
192  
193 yf::Zoom::Zoom() : m_p(new Impl)
194 {
195 }
196
197 yf::Zoom::~Zoom()
198 {  // must have a destructor because of boost::scoped_ptr
199 }
200
201 void yf::Zoom::configure(const xmlNode *xmlnode, bool test_only,
202                          const char *path)
203 {
204     m_p->configure(xmlnode, test_only, path);
205 }
206
207 void yf::Zoom::process(mp::Package &package) const
208 {
209     m_p->process(package);
210 }
211
212
213 // define Implementation stuff
214
215 yf::Zoom::Backend::Backend(SearchablePtr ptr) : sptr(ptr)
216 {
217     m_apdu_wrbuf = wrbuf_alloc();
218     m_connection = ZOOM_connection_create(0);
219     ZOOM_connection_save_apdu_wrbuf(m_connection, m_apdu_wrbuf);
220     m_resultset = 0;
221     xsp = 0;
222 }
223
224 yf::Zoom::Backend::~Backend()
225 {
226     if (xsp)
227         xsltFreeStylesheet(xsp);
228     ZOOM_connection_destroy(m_connection);
229     ZOOM_resultset_destroy(m_resultset);
230 }
231
232
233 void yf::Zoom::Backend::get_zoom_error(int *error, char **addinfo,
234                                        ODR odr)
235 {
236     const char *msg = 0;
237     const char *zoom_addinfo = 0;
238     const char *dset = 0;
239     int error0 = ZOOM_connection_error_x(m_connection, &msg,
240                                          &zoom_addinfo, &dset);
241     if (error0)
242     {
243         if (!dset)
244             dset = "Unknown";
245         
246         if (!strcmp(dset, "info:srw/diagnostic/1"))
247             *error = yaz_diag_srw_to_bib1(error0);
248         else if (!strcmp(dset, "Bib-1"))
249             *error = error0;
250         else
251             *error = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
252         
253         *addinfo = (char *) odr_malloc(
254             odr, 30 + strlen(dset) + strlen(msg) +
255             (zoom_addinfo ? strlen(zoom_addinfo) : 0));
256         **addinfo = '\0';
257         if (zoom_addinfo && *zoom_addinfo)
258         {
259             strcpy(*addinfo, zoom_addinfo);
260             strcat(*addinfo, " ");
261         }
262         sprintf(*addinfo + strlen(*addinfo), "(%s %d %s)", dset, error0, msg);
263     }
264 }
265
266 void yf::Zoom::Backend::connect(std::string zurl,
267                                 int *error, char **addinfo,
268                                 ODR odr)
269 {
270     ZOOM_connection_connect(m_connection, zurl.c_str(), 0);
271     get_zoom_error(error, addinfo, odr);
272 }
273
274 void yf::Zoom::Backend::search(ZOOM_query q, Odr_int *hits,
275                                int *error, char **addinfo, ODR odr)
276 {
277     m_resultset = ZOOM_connection_search(m_connection, q);
278     get_zoom_error(error, addinfo, odr);
279     if (*error == 0)
280         *hits = ZOOM_resultset_size(m_resultset);
281     else
282         *hits = 0;
283 }
284
285 void yf::Zoom::Backend::present(Odr_int start, Odr_int number,
286                                 ZOOM_record *recs,
287                                 int *error, char **addinfo, ODR odr)
288 {
289     ZOOM_resultset_records(m_resultset, recs, start, number);
290     get_zoom_error(error, addinfo, odr);
291 }
292
293 void yf::Zoom::Backend::set_option(const char *name, const char *value)
294 {
295     ZOOM_connection_option_set(m_connection, name, value);
296     if (m_resultset)
297         ZOOM_resultset_option_set(m_resultset, name, value);
298 }
299
300 void yf::Zoom::Backend::set_option(const char *name, std::string value)
301 {
302     set_option(name, value.c_str());
303 }
304
305 const char *yf::Zoom::Backend::get_option(const char *name)
306 {
307     return ZOOM_connection_option_get(m_connection, name);
308 }
309
310 yf::Zoom::Searchable::Searchable(CCL_bibset base)
311 {
312     piggyback = true;
313     use_turbomarc = true;
314     sortStrategy = "embed";
315     urlRecipe = "${md-electronic-url}";
316     ccl_bibset = ccl_qual_dup(base);
317 }
318
319 yf::Zoom::Searchable::~Searchable()
320 {
321     ccl_qual_rm(&ccl_bibset);
322 }
323
324 yf::Zoom::Frontend::Frontend(Impl *impl) : 
325     m_p(impl), m_is_virtual(false), m_in_use(true)
326 {
327 }
328
329 yf::Zoom::Frontend::~Frontend()
330 {
331 }
332
333 yf::Zoom::FrontendPtr yf::Zoom::Impl::get_frontend(mp::Package &package)
334 {
335     boost::mutex::scoped_lock lock(m_mutex);
336
337     std::map<mp::Session,yf::Zoom::FrontendPtr>::iterator it;
338     
339     while(true)
340     {
341         it = m_clients.find(package.session());
342         if (it == m_clients.end())
343             break;
344         
345         if (!it->second->m_in_use)
346         {
347             it->second->m_in_use = true;
348             return it->second;
349         }
350         m_cond_session_ready.wait(lock);
351     }
352     FrontendPtr f(new Frontend(this));
353     m_clients[package.session()] = f;
354     f->m_in_use = true;
355     return f;
356 }
357
358 void yf::Zoom::Impl::release_frontend(mp::Package &package)
359 {
360     boost::mutex::scoped_lock lock(m_mutex);
361     std::map<mp::Session,yf::Zoom::FrontendPtr>::iterator it;
362     
363     it = m_clients.find(package.session());
364     if (it != m_clients.end())
365     {
366         if (package.session().is_closed())
367         {
368             m_clients.erase(it);
369         }
370         else
371         {
372             it->second->m_in_use = false;
373         }
374         m_cond_session_ready.notify_all();
375     }
376 }
377
378 yf::Zoom::Impl::Impl() :
379     apdu_log(false), element_transform("pz2") , element_raw("raw")
380 {
381     bibset = ccl_qual_mk();
382
383     srand((unsigned int) time(0));
384 }
385
386 yf::Zoom::Impl::~Impl()
387
388     ccl_qual_rm(&bibset);
389 }
390
391 yf::Zoom::SearchablePtr yf::Zoom::Impl::parse_torus_record(const xmlNode *ptr)
392 {
393     Zoom::SearchablePtr s(new Searchable(bibset));
394     
395     for (ptr = ptr->children; ptr; ptr = ptr->next)
396     {
397         if (ptr->type != XML_ELEMENT_NODE)
398             continue;
399         if (!strcmp((const char *) ptr->name, "layer"))
400             ptr = ptr->children;
401         else if (!strcmp((const char *) ptr->name,
402                          "authentication"))
403         {
404             s->authentication = mp::xml::get_text(ptr);
405         }
406         else if (!strcmp((const char *) ptr->name,
407                          "cfAuth"))
408         {
409             s->cfAuth = mp::xml::get_text(ptr);
410         } 
411         else if (!strcmp((const char *) ptr->name,
412                          "cfProxy"))
413         {
414             s->cfProxy = mp::xml::get_text(ptr);
415         }  
416         else if (!strcmp((const char *) ptr->name,
417                          "cfSubDB"))
418         {
419             s->cfSubDB = mp::xml::get_text(ptr);
420         }  
421         else if (!strcmp((const char *) ptr->name,
422                          "contentConnector"))
423         {
424             s->contentConnector = mp::xml::get_text(ptr);
425         }  
426         else if (!strcmp((const char *) ptr->name, "udb"))
427         {
428             s->udb = mp::xml::get_text(ptr);
429         }
430         else if (!strcmp((const char *) ptr->name, "zurl"))
431         {
432             s->target = mp::xml::get_text(ptr);
433         }
434         else if (!strcmp((const char *) ptr->name, "sru"))
435         {
436             s->sru = mp::xml::get_text(ptr);
437         }
438         else if (!strcmp((const char *) ptr->name, "SRUVersion") ||
439                  !strcmp((const char *) ptr->name, "sruVersion"))
440         {
441             s->sru_version = mp::xml::get_text(ptr);
442         }
443         else if (!strcmp((const char *) ptr->name,
444                          "queryEncoding"))
445         {
446             s->query_encoding = mp::xml::get_text(ptr);
447         }
448         else if (!strcmp((const char *) ptr->name,
449                          "piggyback"))
450         {
451             s->piggyback = mp::xml::get_bool(ptr, true);
452         }
453         else if (!strcmp((const char *) ptr->name,
454                          "requestSyntax"))
455         {
456             s->request_syntax = mp::xml::get_text(ptr);
457         }
458         else if (!strcmp((const char *) ptr->name,
459                          "elementSet"))
460         {
461             s->element_set = mp::xml::get_text(ptr);
462         }
463         else if (!strcmp((const char *) ptr->name,
464                          "recordEncoding"))
465         {
466             s->record_encoding = mp::xml::get_text(ptr);
467         }
468         else if (!strcmp((const char *) ptr->name,
469                          "transform"))
470         {
471             s->transform_xsl_fname = mp::xml::get_text(ptr);
472         }
473         else if (!strcmp((const char *) ptr->name,
474                          "literalTransform"))
475         {
476             s->transform_xsl_content = mp::xml::get_text(ptr);
477         }
478         else if (!strcmp((const char *) ptr->name,
479                          "urlRecipe"))
480         {
481             s->urlRecipe = mp::xml::get_text(ptr);
482         }
483         else if (!strcmp((const char *) ptr->name,
484                          "useTurboMarc"))
485         {
486             ; // useTurboMarc is ignored
487         }
488         else if (!strncmp((const char *) ptr->name,
489                           "cclmap_", 7))
490         {
491             std::string value = mp::xml::get_text(ptr);
492             ccl_qual_fitem(s->ccl_bibset, value.c_str(),
493                            (const char *) ptr->name + 7);
494         }
495         else if (!strncmp((const char *) ptr->name,
496                           "sortmap_", 8))
497         {
498             std::string value = mp::xml::get_text(ptr);
499             s->sortmap[(const char *) ptr->name + 8] = value;
500         }
501         else if (!strcmp((const char *) ptr->name,
502                           "sortStrategy"))
503         {
504             s->sortStrategy = mp::xml::get_text(ptr);
505         }
506     }
507     return s;
508 }
509
510 void yf::Zoom::Impl::configure_local_records(const xmlNode *ptr, bool test_only)
511 {
512     while (ptr && ptr->type != XML_ELEMENT_NODE)
513         ptr = ptr->next;
514     
515     if (ptr)
516     {
517         if (!strcmp((const char *) ptr->name, "records"))
518         {
519             for (ptr = ptr->children; ptr; ptr = ptr->next)
520             {
521                 if (ptr->type != XML_ELEMENT_NODE)
522                     continue;
523                 if (!strcmp((const char *) ptr->name, "record"))
524                 {
525                     SearchablePtr s = parse_torus_record(ptr);
526                     if (s)
527                     {
528                         std::string udb = s->udb;
529                         if (udb.length())
530                             s_map[s->udb] = s;
531                         else
532                         {
533                             throw mp::filter::FilterException
534                                 ("No udb for local torus record");
535                         }
536                     }
537                 }
538                 else
539                 {
540                     throw mp::filter::FilterException
541                         ("Bad element " 
542                          + std::string((const char *) ptr->name)
543                          + " in zoom filter inside element "
544                          "<torus><records>");
545                 }
546             }
547         }
548         else
549         {
550             throw mp::filter::FilterException
551                 ("Bad element " 
552                  + std::string((const char *) ptr->name)
553                  + " in zoom filter inside element <torus>");
554         }
555     }
556 }
557
558 void yf::Zoom::Impl::configure(const xmlNode *ptr, bool test_only,
559                                const char *path)
560 {
561     content_tmp_file = "/tmp/cf.XXXXXX.p";
562     if (path && *path)
563     {
564         file_path = path;
565     }
566     for (ptr = ptr->children; ptr; ptr = ptr->next)
567     {
568         if (ptr->type != XML_ELEMENT_NODE)
569             continue;
570         else if (!strcmp((const char *) ptr->name, "torus"))
571         {
572             const struct _xmlAttr *attr;
573             for (attr = ptr->properties; attr; attr = attr->next)
574             {
575                 if (!strcmp((const char *) attr->name, "url"))
576                     torus_searchable_url = mp::xml::get_text(attr->children);
577                 else if (!strcmp((const char *) attr->name, "content_url"))
578                     torus_content_url = mp::xml::get_text(attr->children);
579                 else if (!strcmp((const char *) attr->name, "realm"))
580                     default_realm = mp::xml::get_text(attr->children);
581                 else if (!strcmp((const char *) attr->name, "xsldir"))
582                     xsldir = mp::xml::get_text(attr->children);
583                 else if (!strcmp((const char *) attr->name, "element_transform"))
584                     element_transform = mp::xml::get_text(attr->children);
585                 else if (!strcmp((const char *) attr->name, "element_raw"))
586                     element_raw = mp::xml::get_text(attr->children);
587                 else if (!strcmp((const char *) attr->name, "proxy"))
588                     proxy = mp::xml::get_text(attr->children);
589                 else
590                     throw mp::filter::FilterException(
591                         "Bad attribute " + std::string((const char *)
592                                                        attr->name));
593             }
594             // If content_url is not given, use value of searchable, to
595             // ensure backwards compatibility
596             if (!torus_content_url.length())
597                 torus_content_url = torus_searchable_url;
598             configure_local_records(ptr->children, test_only);
599         }
600         else if (!strcmp((const char *) ptr->name, "cclmap"))
601         {
602             const char *addinfo = 0;
603             ccl_xml_config(bibset, ptr, &addinfo);
604         }
605         else if (!strcmp((const char *) ptr->name, "fieldmap"))
606         {
607             const struct _xmlAttr *attr;
608             std::string ccl_field;
609             std::string cql_field;
610             for (attr = ptr->properties; attr; attr = attr->next)
611             {
612                 if (!strcmp((const char *) attr->name, "ccl"))
613                     ccl_field = mp::xml::get_text(attr->children);
614                 else if (!strcmp((const char *) attr->name, "cql"))
615                     cql_field = mp::xml::get_text(attr->children);
616                 else
617                     throw mp::filter::FilterException(
618                         "Bad attribute " + std::string((const char *)
619                                                        attr->name));
620             }
621             if (cql_field.length())
622                 fieldmap[cql_field] = ccl_field;
623         }
624         else if (!strcmp((const char *) ptr->name, "contentProxy"))
625         {
626             const struct _xmlAttr *attr;
627             for (attr = ptr->properties; attr; attr = attr->next)
628             {
629                 if (!strcmp((const char *) attr->name, "server"))
630                     content_proxy_server = mp::xml::get_text(attr->children);
631                 else if (!strcmp((const char *) attr->name, "tmp_file"))
632                     content_tmp_file = mp::xml::get_text(attr->children);
633                 else
634                     throw mp::filter::FilterException(
635                         "Bad attribute " + std::string((const char *)
636                                                        attr->name));
637             }
638         }
639         else if (!strcmp((const char *) ptr->name, "log"))
640         { 
641             const struct _xmlAttr *attr;
642             for (attr = ptr->properties; attr; attr = attr->next)
643             {
644                 if (!strcmp((const char *) attr->name, "apdu"))
645                     apdu_log = mp::xml::get_bool(attr->children, false);
646                 else
647                     throw mp::filter::FilterException(
648                         "Bad attribute " + std::string((const char *)
649                                                        attr->name));
650             }
651         }
652         else
653         {
654             throw mp::filter::FilterException
655                 ("Bad element " 
656                  + std::string((const char *) ptr->name)
657                  + " in zoom filter");
658         }
659     }
660 }
661
662 bool yf::Zoom::Frontend::create_content_session(mp::Package &package,
663                                                 BackendPtr b,
664                                                 int *error, char **addinfo,
665                                                 ODR odr,
666                                                 std::string authentication,
667                                                 std::string proxy)
668 {
669     if (b->sptr->contentConnector.length())
670     {
671         char *fname = (char *) xmalloc(m_p->content_tmp_file.length() + 8);
672         strcpy(fname, m_p->content_tmp_file.c_str());
673         char *xx = strstr(fname, "XXXXXX");
674         if (!xx)
675         {
676             xx = fname + strlen(fname);
677             strcat(fname, "XXXXXX");
678         }
679         char tmp_char = xx[6];
680         sprintf(xx, "%06d", ((unsigned) rand()) % 1000000);
681         xx[6] = tmp_char;
682
683         FILE *file = fopen(fname, "w");
684         if (!file)
685         {
686             package.log("zoom", YLOG_WARN|YLOG_ERRNO, "create %s", fname);
687             *error = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
688             *addinfo = (char *)  odr_malloc(odr, 40 + strlen(fname));
689             sprintf(*addinfo, "Could not create %s", fname);
690             xfree(fname);
691             return false;
692         }
693         b->content_session_id.assign(xx, 6);
694         WRBUF w = wrbuf_alloc();
695         wrbuf_puts(w, "#content_proxy\n");
696         wrbuf_printf(w, "connector: %s\n", b->sptr->contentConnector.c_str());
697         if (authentication.length())
698             wrbuf_printf(w, "auth: %s\n", authentication.c_str());
699         if (proxy.length())
700             wrbuf_printf(w, "proxy: %s\n", proxy.c_str());
701
702         fwrite(wrbuf_buf(w), 1, wrbuf_len(w), file);
703         fclose(file);
704         package.log("zoom", YLOG_LOG, "content file: %s", fname);
705         xfree(fname);
706     }
707     return true;
708 }
709
710 yf::Zoom::BackendPtr yf::Zoom::Frontend::get_backend_from_databases(
711     mp::Package &package,
712     std::string &database, int *error, char **addinfo, ODR odr)
713 {
714     std::list<BackendPtr>::const_iterator map_it;
715     if (m_backend && m_backend->m_frontend_database == database)
716         return m_backend;
717
718     std::string input_args;
719     std::string torus_db;
720     size_t db_arg_pos = database.find(',');
721     if (db_arg_pos != std::string::npos)
722     {
723         torus_db = database.substr(0, db_arg_pos);
724         input_args = database.substr(db_arg_pos + 1);
725     }
726     else
727         torus_db = database;
728
729     std::string authentication;
730     std::string content_authentication;
731     std::string proxy;
732     std::string content_proxy;
733     std::string realm = m_p->default_realm;
734
735     const char *param_user = 0;
736     const char *param_password = 0;
737     const char *param_content_user = 0;
738     const char *param_content_password = 0;
739     int no_parms = 0;
740
741     char **names;
742     char **values;
743     int no_out_args = 0;
744     if (input_args.length())
745         no_parms = yaz_uri_to_array(input_args.c_str(),
746                                     odr, &names, &values);
747     // adding 10 because we'll be adding other URL args
748     const char **out_names = (const char **)
749         odr_malloc(odr, (10 + no_parms) * sizeof(*out_names));
750     const char **out_values = (const char **)
751         odr_malloc(odr, (10 + no_parms) * sizeof(*out_values));
752     
753     // may be changed if it's a content connection
754     std::string torus_url = m_p->torus_searchable_url;
755     int i;
756     for (i = 0; i < no_parms; i++)
757     {
758         const char *name = names[i];
759         const char *value = values[i];
760         assert(name);
761         assert(value);
762         if (!strcmp(name, "user"))
763             param_user = value;
764         else if (!strcmp(name, "password"))
765             param_password = value;
766         else if (!strcmp(name, "content-user"))
767             param_content_user = value;
768         else if (!strcmp(name, "content-password"))
769             param_content_password = value;
770         else if (!strcmp(name, "content-proxy"))
771             content_proxy = value;
772         else if (!strcmp(name, "proxy"))
773             proxy = value;
774         else if (!strcmp(name, "cproxysession"))
775         {
776             out_names[no_out_args] = name;
777             out_values[no_out_args++] = value;
778             torus_url = m_p->torus_content_url;
779         }
780         else if (!strcmp(name, "realm"))
781             realm = value;
782         else if (name[0] == 'x' && name[1] == '-')
783         {
784             out_names[no_out_args] = name;
785             out_values[no_out_args++] = value;
786         }
787         else
788         {
789             BackendPtr notfound;
790             char *msg = (char*) odr_malloc(odr, strlen(name) + 30);
791             *error = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
792             sprintf(msg, "Bad database argument: %s", name);
793             *addinfo = msg;
794             return notfound;
795         }
796     }
797     if (param_user)
798     {
799         authentication = std::string(param_user);
800         if (param_password)
801             authentication += "/" + std::string(param_password);
802     }
803     if (param_content_user)
804     {
805         content_authentication = std::string(param_content_user);
806         if (param_content_password)
807             content_authentication += "/" + std::string(param_content_password);
808     }
809     SearchablePtr sptr;
810
811     std::map<std::string,SearchablePtr>::iterator it;
812     it = m_p->s_map.find(torus_db);
813     if (it != m_p->s_map.end())
814         sptr = it->second;
815     else if (torus_url.length() > 0)
816     {
817         xmlDoc *doc = mp::get_searchable(package,torus_url, torus_db,
818                                          realm, m_p->proxy);
819         if (!doc)
820         {
821             *error = YAZ_BIB1_DATABASE_DOES_NOT_EXIST;
822             *addinfo = odr_strdup(odr, database.c_str());
823             BackendPtr b;
824             return b;
825         }
826         const xmlNode *ptr = xmlDocGetRootElement(doc);
827         if (ptr)
828         {   // presumably ptr is a records element node
829             // parse first record in document
830             for (ptr = ptr->children; ptr; ptr = ptr->next)
831             {
832                 if (ptr->type == XML_ELEMENT_NODE
833                     && !strcmp((const char *) ptr->name, "record"))
834                 {
835                     if (sptr)
836                     {
837                         *error = YAZ_BIB1_UNSPECIFIED_ERROR;
838                         *addinfo = (char*) odr_malloc(odr, 40 + database.length()),
839                         sprintf(*addinfo, "multiple records for udb=%s",
840                                  database.c_str());
841                         xmlFreeDoc(doc);
842                         BackendPtr b;
843                         return b;
844                     }
845                     sptr = m_p->parse_torus_record(ptr);
846                 }
847             }
848         }
849         xmlFreeDoc(doc);
850     }
851
852     if (!sptr)
853     {
854         *error = YAZ_BIB1_DATABASE_DOES_NOT_EXIST;
855         *addinfo = odr_strdup(odr, database.c_str());
856         BackendPtr b;
857         return b;
858     }
859         
860     xsltStylesheetPtr xsp = 0;
861     if (sptr->transform_xsl_content.length())
862     {
863         xmlDoc *xsp_doc = xmlParseMemory(sptr->transform_xsl_content.c_str(),
864                                          sptr->transform_xsl_content.length());
865         if (!xsp_doc)
866         {
867             *error = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
868             *addinfo = (char *) odr_malloc(odr, 40);
869             sprintf(*addinfo, "xmlParseMemory failed");
870             BackendPtr b;
871             return b;
872         }
873         xsp = xsltParseStylesheetDoc(xsp_doc);
874         if (!xsp)
875         {
876             *error = YAZ_BIB1_DATABASE_DOES_NOT_EXIST;
877             *addinfo = odr_strdup(odr, "xsltParseStylesheetDoc failed");
878             BackendPtr b;
879             xmlFreeDoc(xsp_doc);
880             return b;
881         }
882     }
883     else if (sptr->transform_xsl_fname.length())
884     {
885         const char *path = 0;
886
887         if (m_p->xsldir.length())
888             path = m_p->xsldir.c_str();
889         else
890             path = m_p->file_path.c_str();
891         std::string fname;
892
893         char fullpath[1024];
894         char *cp = yaz_filepath_resolve(sptr->transform_xsl_fname.c_str(),
895                                         path, 0, fullpath);
896         if (cp)
897             fname.assign(cp);
898         else
899         {
900             *error = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
901             *addinfo = (char *)
902                 odr_malloc(odr, 40 + sptr->transform_xsl_fname.length());
903             sprintf(*addinfo, "File could not be read: %s", 
904                     sptr->transform_xsl_fname.c_str());
905             BackendPtr b;
906             return b;
907         }
908         xmlDoc *xsp_doc = xmlParseFile(fname.c_str());
909         if (!xsp_doc)
910         {
911             *error = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
912             *addinfo = (char *) odr_malloc(odr, 40 + fname.length());
913             sprintf(*addinfo, "xmlParseFile failed. File: %s", fname.c_str());
914             BackendPtr b;
915             return b;
916         }
917         xsp = xsltParseStylesheetDoc(xsp_doc);
918         if (!xsp)
919         {
920             *error = YAZ_BIB1_DATABASE_DOES_NOT_EXIST;
921             *addinfo = odr_strdup(odr, "xsltParseStylesheetDoc failed");
922             BackendPtr b;
923             xmlFreeDoc(xsp_doc);
924             return b;
925         }
926     }
927
928     m_backend.reset();
929
930     BackendPtr b(new Backend(sptr));
931
932     b->xsp = xsp;
933     b->m_frontend_database = database;
934
935     if (sptr->query_encoding.length())
936         b->set_option("rpnCharset", sptr->query_encoding);
937
938     b->set_option("timeout", "40");
939     
940     if (m_p->apdu_log) 
941         b->set_option("apdulog", "1");
942
943     if (sptr->piggyback && sptr->sru.length())
944         b->set_option("count", "1"); /* some SRU servers INSIST on getting
945                                         maximumRecords > 0 */
946     b->set_option("piggyback", sptr->piggyback ? "1" : "0");
947
948     if (authentication.length() == 0)
949         authentication = sptr->authentication;
950
951     if (proxy.length() == 0)
952         proxy = sptr->cfProxy;
953     
954     if (sptr->cfAuth.length())
955     {
956         // A CF target
957         b->set_option("user", sptr->cfAuth);
958         if (authentication.length())
959         {
960             size_t found = authentication.find('/');
961             if (found != std::string::npos)
962             {
963                 out_names[no_out_args] = "user";
964                 out_values[no_out_args++] =
965                     odr_strdup(odr, authentication.substr(0, found).c_str());
966
967                 out_names[no_out_args] = "password";
968                 out_values[no_out_args++] =
969                     odr_strdup(odr, authentication.substr(found+1).c_str());
970             }
971             else
972             {
973                 out_names[no_out_args] = "user";
974                 out_values[no_out_args++] =
975                     odr_strdup(odr, authentication.c_str());
976             }                
977         }
978         if (proxy.length())
979         {
980             out_names[no_out_args] = "proxy";
981             out_values[no_out_args++] = odr_strdup(odr, proxy.c_str());
982         }
983         if (sptr->cfSubDB.length())
984         {
985             out_names[no_out_args] = "subdatabase";
986             out_values[no_out_args++] = odr_strdup(odr, sptr->cfSubDB.c_str());
987         }
988     }
989     else
990     {
991         size_t found = authentication.find('/');
992         
993         if (sptr->sru.length() && found != std::string::npos)
994         {
995             b->set_option("user", authentication.substr(0, found));
996             b->set_option("password", authentication.substr(found+1));
997         }
998         else
999             b->set_option("user", authentication);
1000
1001         if (proxy.length())
1002             b->set_option("proxy", proxy);
1003     }
1004     std::string url;
1005     if (sptr->sru.length())
1006     {
1007         url = "http://" + sptr->target;
1008         b->set_option("sru", sptr->sru);
1009
1010         if (sptr->sru_version.length())
1011             b->set_option("sru_version", sptr->sru_version);
1012     }
1013     else
1014     {
1015         url = sptr->target;
1016     }
1017     if (no_out_args)
1018     {
1019         char *x_args = 0;
1020         out_names[no_out_args] = 0; // terminate list
1021         
1022         yaz_array_to_uri(&x_args, odr, (char **) out_names,
1023                          (char **) out_values);
1024         url += "," + std::string(x_args);
1025     }
1026     package.log("zoom", YLOG_LOG, "url: %s", url.c_str());
1027     b->connect(url, error, addinfo, odr);
1028     if (*error == 0)
1029         create_content_session(package, b, error, addinfo, odr,
1030                                content_authentication.length() ?
1031                                content_authentication : authentication,
1032                                content_proxy.length() ? content_proxy : proxy);
1033     if (*error == 0)
1034         m_backend = b;
1035     return b;
1036 }
1037
1038 void yf::Zoom::Frontend::prepare_elements(BackendPtr b,
1039                                           Odr_oid *preferredRecordSyntax,
1040                                           const char *element_set_name,
1041                                           bool &enable_pz2_retrieval,
1042                                           bool &enable_pz2_transform,
1043                                           bool &assume_marc8_charset)
1044
1045 {
1046     char oid_name_str[OID_STR_MAX];
1047     const char *syntax_name = 0;
1048     
1049     if (preferredRecordSyntax &&
1050         !oid_oidcmp(preferredRecordSyntax, yaz_oid_recsyn_xml)
1051         && element_set_name)
1052     {
1053         if (!strcmp(element_set_name, m_p->element_transform.c_str()))
1054         {
1055             enable_pz2_retrieval = true;
1056             enable_pz2_transform = true;
1057         }
1058         else if (!strcmp(element_set_name, m_p->element_raw.c_str()))
1059         {
1060             enable_pz2_retrieval = true;
1061         }
1062     }
1063     
1064     if (enable_pz2_retrieval)
1065     {
1066         std::string configured_request_syntax = b->sptr->request_syntax;
1067         if (configured_request_syntax.length())
1068         {
1069             syntax_name = configured_request_syntax.c_str();
1070             const Odr_oid *syntax_oid = 
1071                 yaz_string_to_oid(yaz_oid_std(), CLASS_RECSYN, syntax_name);
1072             if (!oid_oidcmp(syntax_oid, yaz_oid_recsyn_usmarc)
1073                 || !oid_oidcmp(syntax_oid, yaz_oid_recsyn_opac))
1074                 assume_marc8_charset = true;
1075         }
1076     }
1077     else if (preferredRecordSyntax)
1078         syntax_name =
1079             yaz_oid_to_string_buf(preferredRecordSyntax, 0, oid_name_str);
1080
1081     if (b->sptr->sru.length())
1082         syntax_name = "XML";
1083
1084     b->set_option("preferredRecordSyntax", syntax_name);
1085
1086     if (enable_pz2_retrieval)
1087     {
1088         element_set_name = 0;
1089         if (b->sptr->element_set.length())
1090             element_set_name = b->sptr->element_set.c_str();
1091     }
1092
1093     b->set_option("elementSetName", element_set_name);
1094     if (b->sptr->sru.length() && element_set_name)
1095         b->set_option("schema", element_set_name);
1096 }
1097
1098 Z_Records *yf::Zoom::Frontend::get_records(Package &package,
1099                                            Odr_int start,
1100                                            Odr_int number_to_present,
1101                                            int *error,
1102                                            char **addinfo,
1103                                            Odr_int *number_of_records_returned,
1104                                            ODR odr,
1105                                            BackendPtr b,
1106                                            Odr_oid *preferredRecordSyntax,
1107                                            const char *element_set_name)
1108 {
1109     *number_of_records_returned = 0;
1110     Z_Records *records = 0;
1111     bool enable_pz2_retrieval = false; // whether target profile is used
1112     bool enable_pz2_transform = false; // whether XSLT is used as well
1113     bool assume_marc8_charset = false;
1114
1115     prepare_elements(b, preferredRecordSyntax,
1116                      element_set_name,
1117                      enable_pz2_retrieval,
1118                      enable_pz2_transform,
1119                      assume_marc8_charset);
1120
1121     package.log("zoom", YLOG_LOG, "pz2_retrieval: %s . pz2_transform: %s",
1122                 enable_pz2_retrieval ? "yes" : "no",
1123                 enable_pz2_transform ? "yes" : "no");
1124
1125     if (start < 0 || number_to_present <=0)
1126         return records;
1127     
1128     if (number_to_present > 10000)
1129         number_to_present = 10000;
1130
1131     ZOOM_record *recs = (ZOOM_record *)
1132         odr_malloc(odr, (size_t) number_to_present * sizeof(*recs));
1133
1134     b->present(start, number_to_present, recs, error, addinfo, odr);
1135
1136     int i = 0;
1137     if (!*error)
1138     {
1139         for (i = 0; i < number_to_present; i++)
1140             if (!recs[i])
1141                 break;
1142     }
1143     if (i > 0)
1144     {  // only return records if no error and at least one record
1145         char *odr_database = odr_strdup(odr,
1146                                         b->m_frontend_database.c_str());
1147         Z_NamePlusRecordList *npl = (Z_NamePlusRecordList *)
1148             odr_malloc(odr, sizeof(*npl));
1149         *number_of_records_returned = i;
1150         npl->num_records = i;
1151         npl->records = (Z_NamePlusRecord **)
1152             odr_malloc(odr, i * sizeof(*npl->records));
1153         for (i = 0; i < number_to_present; i++)
1154         {
1155             Z_NamePlusRecord *npr = 0;
1156             const char *addinfo;
1157
1158             package.log("zoom", YLOG_LOG, "Inspecting record at position %d",
1159                         start + i);
1160             int sur_error = ZOOM_record_error(recs[i], 0 /* msg */,
1161                                               &addinfo, 0 /* diagset */);
1162                 
1163             if (sur_error)
1164             {
1165                 log_diagnostic(package, sur_error, addinfo);
1166                 npr = zget_surrogateDiagRec(odr, odr_database, sur_error,
1167                                             addinfo);
1168             }
1169             else if (enable_pz2_retrieval)
1170             {
1171                 char rec_type_str[100];
1172                 const char *record_encoding = 0;
1173
1174                 if (b->sptr->record_encoding.length())
1175                     record_encoding = b->sptr->record_encoding.c_str();
1176                 else if (assume_marc8_charset)
1177                     record_encoding = "marc8";
1178
1179                 strcpy(rec_type_str, b->sptr->use_turbomarc ? "txml" : "xml");
1180                 if (record_encoding)
1181                 {
1182                     strcat(rec_type_str, "; charset=");
1183                     strcat(rec_type_str, record_encoding);
1184                 }
1185
1186                 package.log("zoom", YLOG_LOG, "Getting record of type %s",
1187                             rec_type_str);
1188                 int rec_len;
1189                 xmlChar *xmlrec_buf = 0;
1190                 const char *rec_buf = ZOOM_record_get(recs[i], rec_type_str,
1191                                                       &rec_len);
1192                 if (!rec_buf && !npr)
1193                 {
1194                     std::string addinfo("ZOOM_record_get failed for type ");
1195
1196                     int error = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
1197                     addinfo += rec_type_str;
1198                     log_diagnostic(package, error, addinfo.c_str());
1199                     npr = zget_surrogateDiagRec(odr, odr_database,
1200                                                 error, addinfo.c_str());
1201                 }
1202                 else
1203                 {
1204                     package.log_write(rec_buf, rec_len);
1205                     package.log_write("\r\n", 2);
1206                 }
1207
1208                 if (rec_buf && b->xsp && enable_pz2_transform)
1209                 {
1210                     xmlDoc *rec_doc = xmlParseMemory(rec_buf, rec_len);
1211                     if (!rec_doc)
1212                     {
1213                         const char *addinfo = "xml parse failed for record";
1214                         int error = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
1215                         log_diagnostic(package, error, addinfo);
1216                         npr = zget_surrogateDiagRec(
1217                             odr, odr_database, error, addinfo);
1218                     }
1219                     else
1220                     { 
1221                         xmlDoc *rec_res = 
1222                             xsltApplyStylesheet(b->xsp, rec_doc, 0);
1223
1224                         if (rec_res)
1225                         {
1226                             xsltSaveResultToString(&xmlrec_buf, &rec_len,
1227                                                    rec_res, b->xsp);
1228                             rec_buf = (const char *) xmlrec_buf;
1229                             package.log("zoom", YLOG_LOG, "xslt successful");
1230                             package.log_write(rec_buf, rec_len);
1231
1232                             xmlFreeDoc(rec_res);
1233                         }
1234                         if (!rec_buf)
1235                         {
1236                             std::string addinfo;
1237                             int error =
1238                                 YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
1239
1240                             addinfo = "xslt apply failed for "
1241                                 + b->sptr->transform_xsl_fname;
1242                             log_diagnostic(package, error, addinfo.c_str());
1243                             npr = zget_surrogateDiagRec(
1244                                 odr, odr_database, error, addinfo.c_str());
1245                         }
1246                         xmlFreeDoc(rec_doc);
1247                     }
1248                 }
1249
1250                 if (rec_buf)
1251                 {
1252                     xmlDoc *doc = xmlParseMemory(rec_buf, rec_len);
1253                     std::string res = 
1254                         mp::xml::url_recipe_handle(doc, b->sptr->urlRecipe);
1255                     if (res.length() && b->content_session_id.length())
1256                     {
1257                         size_t off = res.find_first_of("://");
1258                         if (off != std::string::npos)
1259                         {
1260                             char tmp[1024];
1261                             sprintf(tmp, "%s.%s/",
1262                                     b->content_session_id.c_str(),
1263                                     m_p->content_proxy_server.c_str());
1264                             res.insert(off + 3, tmp);
1265                         }
1266                     }
1267                     if (res.length())
1268                     {
1269                         xmlNode *ptr = xmlDocGetRootElement(doc);
1270                         while (ptr && ptr->type != XML_ELEMENT_NODE)
1271                             ptr = ptr->next;
1272                         xmlNode *c = 
1273                             xmlNewChild(ptr, 0, BAD_CAST "metadata", 0);
1274                         xmlNewProp(c, BAD_CAST "type", BAD_CAST
1275                                    "generated-url");
1276                         xmlNode * t = xmlNewText(BAD_CAST res.c_str());
1277                         xmlAddChild(c, t);
1278
1279                         if (xmlrec_buf)
1280                             xmlFree(xmlrec_buf);
1281
1282                         xmlDocDumpMemory(doc, &xmlrec_buf, &rec_len);
1283                         rec_buf = (const char *) xmlrec_buf;
1284                     }
1285                     xmlFreeDoc(doc);
1286                 }
1287                 if (!npr)
1288                 {
1289                     if (!rec_buf)
1290                         npr = zget_surrogateDiagRec(
1291                             odr, odr_database, 
1292                             YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS,
1293                             rec_type_str);
1294                     else
1295                     {
1296                         npr = (Z_NamePlusRecord *)
1297                             odr_malloc(odr, sizeof(*npr));
1298                         npr->databaseName = odr_database;
1299                         npr->which = Z_NamePlusRecord_databaseRecord;
1300                         npr->u.databaseRecord =
1301                             z_ext_record_xml(odr, rec_buf, rec_len);
1302                     }
1303                 }
1304                 if (xmlrec_buf)
1305                     xmlFree(xmlrec_buf);
1306             }
1307             else
1308             {
1309                 Z_External *ext =
1310                     (Z_External *) ZOOM_record_get(recs[i], "ext", 0);
1311                 if (ext)
1312                 {
1313                     npr = (Z_NamePlusRecord *) odr_malloc(odr, sizeof(*npr));
1314                     npr->databaseName = odr_database;
1315                     npr->which = Z_NamePlusRecord_databaseRecord;
1316                     npr->u.databaseRecord = ext;
1317                 }
1318                 else
1319                 {
1320                     npr = zget_surrogateDiagRec(
1321                         odr, odr_database, 
1322                         YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS,
1323                         "ZOOM_record, type ext");
1324                 }
1325             }
1326             npl->records[i] = npr;
1327         }
1328         records = (Z_Records*) odr_malloc(odr, sizeof(*records));
1329         records->which = Z_Records_DBOSD;
1330         records->u.databaseOrSurDiagnostics = npl;
1331     }
1332     return records;
1333 }
1334
1335 struct cql_node *yf::Zoom::Impl::convert_cql_fields(struct cql_node *cn,
1336                                                     ODR odr)
1337 {
1338     struct cql_node *r = 0;
1339     if (!cn)
1340         return 0;
1341     switch (cn->which)
1342     {
1343     case CQL_NODE_ST:
1344         if (cn->u.st.index)
1345         {
1346             std::map<std::string,std::string>::const_iterator it;
1347             it = fieldmap.find(cn->u.st.index);
1348             if (it == fieldmap.end())
1349                 return cn;
1350             if (it->second.length())
1351                 cn->u.st.index = odr_strdup(odr, it->second.c_str());
1352             else
1353                 cn->u.st.index = 0;
1354         }
1355         break;
1356     case CQL_NODE_BOOL:
1357         r = convert_cql_fields(cn->u.boolean.left, odr);
1358         if (!r)
1359             r = convert_cql_fields(cn->u.boolean.right, odr);
1360         break;
1361     case CQL_NODE_SORT:
1362         r = convert_cql_fields(cn->u.sort.search, odr);
1363         break;
1364     }
1365     return r;
1366 }
1367
1368 void yf::Zoom::Frontend::log_diagnostic(mp::Package &package,
1369                                         int error, const char *addinfo)
1370 {
1371     const char *err_msg = yaz_diag_bib1_str(error);
1372     if (addinfo)
1373         package.log("zoom", YLOG_WARN, "Diagnostic %d %s: %s",
1374                     error, err_msg, addinfo);
1375     else
1376         package.log("zoom", YLOG_WARN, "Diagnostic %d %s:",
1377                     error, err_msg);
1378 }
1379
1380 void yf::Zoom::Frontend::handle_search(mp::Package &package)
1381 {
1382     Z_GDU *gdu = package.request().get();
1383     Z_APDU *apdu_req = gdu->u.z3950;
1384     Z_APDU *apdu_res = 0;
1385     mp::odr odr;
1386     Z_SearchRequest *sr = apdu_req->u.searchRequest;
1387     if (sr->num_databaseNames != 1)
1388     {
1389         int error = YAZ_BIB1_TOO_MANY_DATABASES_SPECIFIED;
1390         log_diagnostic(package, error, 0);
1391         apdu_res = odr.create_searchResponse(apdu_req, error, 0);
1392         package.response() = apdu_res;
1393         return;
1394     }
1395
1396     int error = 0;
1397     char *addinfo = 0;
1398     std::string db(sr->databaseNames[0]);
1399     BackendPtr b = get_backend_from_databases(package, db, &error,
1400                                               &addinfo, odr);
1401     if (error)
1402     {
1403         log_diagnostic(package, error, addinfo);
1404         apdu_res = odr.create_searchResponse(apdu_req, error, addinfo);
1405         package.response() = apdu_res;
1406         return;
1407     }
1408
1409     b->set_option("setname", "default");
1410
1411     bool enable_pz2_retrieval = false;
1412     bool enable_pz2_transform = false;
1413     bool assume_marc8_charset = false;
1414     prepare_elements(b, sr->preferredRecordSyntax, 0 /*element_set_name */,
1415                      enable_pz2_retrieval,
1416                      enable_pz2_transform,
1417                      assume_marc8_charset);
1418
1419     Odr_int hits = 0;
1420     Z_Query *query = sr->query;
1421     WRBUF ccl_wrbuf = 0;
1422     WRBUF pqf_wrbuf = 0;
1423     std::string sortkeys;
1424
1425     if (query->which == Z_Query_type_1 || query->which == Z_Query_type_101)
1426     {
1427         // RPN
1428         pqf_wrbuf = wrbuf_alloc();
1429         yaz_rpnquery_to_wrbuf(pqf_wrbuf, query->u.type_1);
1430     }
1431     else if (query->which == Z_Query_type_2)
1432     {
1433         // CCL
1434         ccl_wrbuf = wrbuf_alloc();
1435         wrbuf_write(ccl_wrbuf, (const char *) query->u.type_2->buf,
1436                     query->u.type_2->len);
1437     }
1438     else if (query->which == Z_Query_type_104 &&
1439              query->u.type_104->which == Z_External_CQL)
1440     {
1441         // CQL
1442         const char *cql = query->u.type_104->u.cql;
1443         CQL_parser cp = cql_parser_create();
1444         int r = cql_parser_string(cp, cql);
1445         package.log("zoom", YLOG_LOG, "CQL: %s", cql);
1446         if (r)
1447         {
1448             cql_parser_destroy(cp);
1449             error = YAZ_BIB1_MALFORMED_QUERY;
1450             const char *addinfo = "CQL syntax error";
1451             log_diagnostic(package, error, addinfo);
1452             apdu_res = 
1453                 odr.create_searchResponse(apdu_req, error, addinfo);
1454             package.response() = apdu_res;
1455             return;
1456         }
1457         struct cql_node *cn = cql_parser_result(cp);
1458         struct cql_node *cn_error = m_p->convert_cql_fields(cn, odr);
1459         if (cn_error)
1460         {
1461             // hopefully we are getting a ptr to a index+relation+term node
1462             error = YAZ_BIB1_UNSUPP_USE_ATTRIBUTE;
1463             addinfo = 0;
1464             if (cn_error->which == CQL_NODE_ST)
1465                 addinfo = cn_error->u.st.index;
1466             
1467             log_diagnostic(package, error, addinfo);
1468             apdu_res = odr.create_searchResponse(apdu_req, error, addinfo);
1469             package.response() = apdu_res;
1470             return;
1471         }
1472         char ccl_buf[1024];
1473
1474         r = cql_to_ccl_buf(cn, ccl_buf, sizeof(ccl_buf));
1475         if (r == 0)
1476         {
1477             ccl_wrbuf = wrbuf_alloc();
1478             wrbuf_puts(ccl_wrbuf, ccl_buf);
1479             
1480             WRBUF sru_sortkeys_wrbuf = wrbuf_alloc();
1481
1482             cql_sortby_to_sortkeys(cn, wrbuf_vp_puts, sru_sortkeys_wrbuf);
1483             WRBUF sort_spec_wrbuf = wrbuf_alloc();
1484             yaz_srw_sortkeys_to_sort_spec(wrbuf_cstr(sru_sortkeys_wrbuf),
1485                                           sort_spec_wrbuf);
1486             wrbuf_destroy(sru_sortkeys_wrbuf);
1487
1488             yaz_tok_cfg_t tc = yaz_tok_cfg_create();
1489             yaz_tok_parse_t tp =
1490                 yaz_tok_parse_buf(tc, wrbuf_cstr(sort_spec_wrbuf));
1491             yaz_tok_cfg_destroy(tc);
1492
1493             /* go through sortspec and map fields */
1494             int token = yaz_tok_move(tp);
1495             while (token != YAZ_TOK_EOF)
1496             {
1497                 if (token == YAZ_TOK_STRING)
1498                 {
1499                     const char *field = yaz_tok_parse_string(tp);
1500                     std::map<std::string,std::string>::iterator it;
1501                     it = b->sptr->sortmap.find(field);
1502                     if (it != b->sptr->sortmap.end())
1503                         sortkeys += it->second;
1504                     else
1505                         sortkeys += field;
1506                 }
1507                 sortkeys += " ";
1508                 token = yaz_tok_move(tp);
1509                 if (token == YAZ_TOK_STRING)
1510                 {
1511                     sortkeys += yaz_tok_parse_string(tp);
1512                 }
1513                 if (token != YAZ_TOK_EOF)
1514                 {
1515                     sortkeys += " ";
1516                     token = yaz_tok_move(tp);
1517                 }
1518             }
1519             yaz_tok_parse_destroy(tp);
1520             wrbuf_destroy(sort_spec_wrbuf);
1521         }
1522         cql_parser_destroy(cp);
1523         if (r)
1524         {
1525             error = YAZ_BIB1_MALFORMED_QUERY;
1526             const char *addinfo = "CQL to CCL conversion error";
1527
1528             log_diagnostic(package, error, addinfo);
1529             apdu_res = odr.create_searchResponse(apdu_req, error, addinfo);
1530             package.response() = apdu_res;
1531             return;
1532         }
1533     }
1534     else
1535     {
1536         error = YAZ_BIB1_QUERY_TYPE_UNSUPP;
1537         const char *addinfo = 0;
1538         log_diagnostic(package, error, addinfo);
1539         apdu_res =  odr.create_searchResponse(apdu_req, error, addinfo);
1540         package.response() = apdu_res;
1541         return;
1542     }
1543
1544     if (ccl_wrbuf)
1545     {
1546         // CCL to PQF
1547         assert(pqf_wrbuf == 0);
1548         int cerror, cpos;
1549         struct ccl_rpn_node *cn;
1550         package.log("zoom", YLOG_LOG, "CCL: %s", wrbuf_cstr(ccl_wrbuf));
1551         cn = ccl_find_str(b->sptr->ccl_bibset, wrbuf_cstr(ccl_wrbuf),
1552                           &cerror, &cpos);
1553         wrbuf_destroy(ccl_wrbuf);
1554         if (!cn)
1555         {
1556             char *addinfo = odr_strdup(odr, ccl_err_msg(cerror));
1557             error = YAZ_BIB1_MALFORMED_QUERY;
1558
1559             switch (cerror)
1560             {
1561             case CCL_ERR_UNKNOWN_QUAL:
1562                 error = YAZ_BIB1_UNSUPP_USE_ATTRIBUTE;
1563                 break;
1564             case CCL_ERR_TRUNC_NOT_LEFT: 
1565             case CCL_ERR_TRUNC_NOT_RIGHT:
1566             case CCL_ERR_TRUNC_NOT_BOTH:
1567                 error = YAZ_BIB1_UNSUPP_TRUNCATION_ATTRIBUTE;
1568                 break;
1569             }
1570             log_diagnostic(package, error, addinfo);
1571             apdu_res = odr.create_searchResponse(apdu_req, error, addinfo);
1572             package.response() = apdu_res;
1573             return;
1574         }
1575         pqf_wrbuf = wrbuf_alloc();
1576         ccl_pquery(pqf_wrbuf, cn);
1577         package.log("zoom", YLOG_LOG, "RPN: %s", wrbuf_cstr(pqf_wrbuf));
1578         ccl_rpn_delete(cn);
1579     }
1580     
1581     assert(pqf_wrbuf);
1582
1583     ZOOM_query q = ZOOM_query_create();
1584     ZOOM_query_sortby2(q, b->sptr->sortStrategy.c_str(), sortkeys.c_str());
1585
1586     if (b->get_option("sru"))
1587     {
1588         int status = 0;
1589         Z_RPNQuery *zquery;
1590         zquery = p_query_rpn(odr, wrbuf_cstr(pqf_wrbuf));
1591         WRBUF wrb = wrbuf_alloc();
1592             
1593         if (!strcmp(b->get_option("sru"), "solr"))
1594         {
1595             solr_transform_t cqlt = solr_transform_create();
1596             
1597             status = solr_transform_rpn2solr_wrbuf(cqlt, wrb, zquery);
1598             
1599             solr_transform_close(cqlt);
1600         }
1601         else
1602         {
1603             cql_transform_t cqlt = cql_transform_create();
1604             
1605             status = cql_transform_rpn2cql_wrbuf(cqlt, wrb, zquery);
1606             
1607             cql_transform_close(cqlt);
1608         }
1609         if (status == 0)
1610         {
1611             ZOOM_query_cql(q, wrbuf_cstr(wrb));
1612             package.log("zoom", YLOG_LOG, "CQL: %s", wrbuf_cstr(wrb));
1613             b->search(q, &hits, &error, &addinfo, odr);
1614         }
1615         ZOOM_query_destroy(q);
1616         
1617         wrbuf_destroy(wrb);
1618         wrbuf_destroy(pqf_wrbuf);
1619         if (status)
1620         {
1621             error = YAZ_BIB1_MALFORMED_QUERY;
1622             const char *addinfo = "can not convert from RPN to CQL/SOLR";
1623             log_diagnostic(package, error, addinfo);
1624             apdu_res = odr.create_searchResponse(apdu_req, error, addinfo);
1625             package.response() = apdu_res;
1626             return;
1627         }
1628     }
1629     else
1630     {
1631         ZOOM_query_prefix(q, wrbuf_cstr(pqf_wrbuf));
1632         package.log("zoom", YLOG_LOG, "search PQF: %s", wrbuf_cstr(pqf_wrbuf));
1633         b->search(q, &hits, &error, &addinfo, odr);
1634         ZOOM_query_destroy(q);
1635         wrbuf_destroy(pqf_wrbuf);
1636     }
1637
1638     const char *element_set_name = 0;
1639     Odr_int number_to_present = 0;
1640     if (!error)
1641         mp::util::piggyback_sr(sr, hits, number_to_present, &element_set_name);
1642     
1643     Odr_int number_of_records_returned = 0;
1644     Z_Records *records = get_records(
1645         package,
1646         0, number_to_present, &error, &addinfo,
1647         &number_of_records_returned, odr, b, sr->preferredRecordSyntax,
1648         element_set_name);
1649     if (error)
1650         log_diagnostic(package, error, addinfo);
1651     apdu_res = odr.create_searchResponse(apdu_req, error, addinfo);
1652     if (records)
1653     {
1654         apdu_res->u.searchResponse->records = records;
1655         apdu_res->u.searchResponse->numberOfRecordsReturned =
1656             odr_intdup(odr, number_of_records_returned);
1657     }
1658     apdu_res->u.searchResponse->resultCount = odr_intdup(odr, hits);
1659     package.response() = apdu_res;
1660 }
1661
1662 void yf::Zoom::Frontend::handle_present(mp::Package &package)
1663 {
1664     Z_GDU *gdu = package.request().get();
1665     Z_APDU *apdu_req = gdu->u.z3950;
1666     Z_APDU *apdu_res = 0;
1667     Z_PresentRequest *pr = apdu_req->u.presentRequest;
1668
1669     mp::odr odr;
1670     if (!m_backend)
1671     {
1672         package.response() = odr.create_presentResponse(
1673             apdu_req, YAZ_BIB1_SPECIFIED_RESULT_SET_DOES_NOT_EXIST, 0);
1674         return;
1675     }
1676     const char *element_set_name = 0;
1677     Z_RecordComposition *comp = pr->recordComposition;
1678     if (comp && comp->which != Z_RecordComp_simple)
1679     {
1680         package.response() = odr.create_presentResponse(
1681             apdu_req, 
1682             YAZ_BIB1_PRESENT_COMP_SPEC_PARAMETER_UNSUPP, 0);
1683         return;
1684     }
1685     if (comp && comp->u.simple->which == Z_ElementSetNames_generic)
1686         element_set_name = comp->u.simple->u.generic;
1687     Odr_int number_of_records_returned = 0;
1688     int error = 0;
1689     char *addinfo = 0;
1690     Z_Records *records = get_records(package,
1691         *pr->resultSetStartPoint - 1, *pr->numberOfRecordsRequested,
1692         &error, &addinfo, &number_of_records_returned, odr, m_backend,
1693         pr->preferredRecordSyntax, element_set_name);
1694
1695     apdu_res = odr.create_presentResponse(apdu_req, error, addinfo);
1696     if (records)
1697     {
1698         apdu_res->u.presentResponse->records = records;
1699         apdu_res->u.presentResponse->numberOfRecordsReturned =
1700             odr_intdup(odr, number_of_records_returned);
1701     }
1702     package.response() = apdu_res;
1703 }
1704
1705 void yf::Zoom::Frontend::handle_package(mp::Package &package)
1706 {
1707     Z_GDU *gdu = package.request().get();
1708     if (!gdu)
1709         ;
1710     else if (gdu->which == Z_GDU_Z3950)
1711     {
1712         Z_APDU *apdu_req = gdu->u.z3950;
1713
1714         if (m_backend)
1715             wrbuf_rewind(m_backend->m_apdu_wrbuf);
1716         if (apdu_req->which == Z_APDU_initRequest)
1717         {
1718             mp::odr odr;
1719             package.response() = odr.create_close(
1720                 apdu_req,
1721                 Z_Close_protocolError,
1722                 "double init");
1723         }
1724         else if (apdu_req->which == Z_APDU_searchRequest)
1725         {
1726             handle_search(package);
1727         }
1728         else if (apdu_req->which == Z_APDU_presentRequest)
1729         {
1730             handle_present(package);
1731         }
1732         else
1733         {
1734             mp::odr odr;
1735             package.response() = odr.create_close(
1736                 apdu_req,
1737                 Z_Close_protocolError,
1738                 "zoom filter cannot handle this APDU");
1739             package.session().close();
1740         }
1741         if (m_backend)
1742         {
1743             WRBUF w = m_backend->m_apdu_wrbuf;
1744             package.log_write(wrbuf_buf(w), wrbuf_len(w));
1745         }
1746     }
1747     else
1748     {
1749         package.session().close();
1750     }
1751 }
1752
1753 void yf::Zoom::Impl::process(mp::Package &package)
1754 {
1755     FrontendPtr f = get_frontend(package);
1756     Z_GDU *gdu = package.request().get();
1757
1758     if (f->m_is_virtual)
1759     {
1760         f->handle_package(package);
1761     }
1762     else if (gdu && gdu->which == Z_GDU_Z3950 && gdu->u.z3950->which ==
1763              Z_APDU_initRequest)
1764     {
1765         Z_InitRequest *req = gdu->u.z3950->u.initRequest;
1766         f->m_init_gdu = gdu;
1767         
1768         mp::odr odr;
1769         Z_APDU *apdu = odr.create_initResponse(gdu->u.z3950, 0, 0);
1770         Z_InitResponse *resp = apdu->u.initResponse;
1771         
1772         int i;
1773         static const int masks[] = {
1774             Z_Options_search,
1775             Z_Options_present,
1776             -1 
1777         };
1778         for (i = 0; masks[i] != -1; i++)
1779             if (ODR_MASK_GET(req->options, masks[i]))
1780                 ODR_MASK_SET(resp->options, masks[i]);
1781         
1782         static const int versions[] = {
1783             Z_ProtocolVersion_1,
1784             Z_ProtocolVersion_2,
1785             Z_ProtocolVersion_3,
1786             -1
1787         };
1788         for (i = 0; versions[i] != -1; i++)
1789             if (ODR_MASK_GET(req->protocolVersion, versions[i]))
1790                 ODR_MASK_SET(resp->protocolVersion, versions[i]);
1791             else
1792                 break;
1793         
1794         *resp->preferredMessageSize = *req->preferredMessageSize;
1795         *resp->maximumRecordSize = *req->maximumRecordSize;
1796         
1797         package.response() = apdu;
1798         f->m_is_virtual = true;
1799     }
1800     else
1801         package.move();
1802
1803     release_frontend(package);
1804 }
1805
1806
1807 static mp::filter::Base* filter_creator()
1808 {
1809     return new mp::filter::Zoom;
1810 }
1811
1812 extern "C" {
1813     struct metaproxy_1_filter_struct metaproxy_1_filter_zoom = {
1814         0,
1815         "zoom",
1816         filter_creator
1817     };
1818 }
1819
1820
1821 /*
1822  * Local variables:
1823  * c-basic-offset: 4
1824  * c-file-style: "Stroustrup"
1825  * indent-tabs-mode: nil
1826  * End:
1827  * vim: shiftwidth=4 tabstop=8 expandtab
1828  */
1829