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