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