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