Implement CQL to CCL fieldmap, bug #4336
[metaproxy-moved-to-github.git] / src / filter_zoom.cpp
1 /* This file is part of Metaproxy.
2    Copyright (C) 2005-2011 Index Data
3
4 Metaproxy is free software; you can redistribute it and/or modify it under
5 the terms of the GNU General Public License as published by the Free
6 Software Foundation; either version 2, or (at your option) any later
7 version.
8
9 Metaproxy is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17 */
18
19 #include "config.hpp"
20 #include "filter_zoom.hpp"
21 #include <yaz/zoom.h>
22 #include <yaz/srw.h>
23 #include <metaproxy/package.hpp>
24 #include <metaproxy/util.hpp>
25 #include "torus.hpp"
26
27 #include <libxslt/xsltutils.h>
28 #include <libxslt/transform.h>
29
30 #include <boost/thread/mutex.hpp>
31 #include <boost/thread/condition.hpp>
32 #include <yaz/ccl.h>
33 #include <yaz/cql.h>
34 #include <yaz/oid_db.h>
35 #include <yaz/diagbib1.h>
36 #include <yaz/log.h>
37 #include <yaz/zgdu.h>
38 #include <yaz/querytowrbuf.h>
39
40 namespace mp = metaproxy_1;
41 namespace yf = mp::filter;
42
43 namespace metaproxy_1 {
44     namespace filter {
45         struct Zoom::Searchable : boost::noncopyable {
46             std::string authentication;
47             std::string cfAuth;
48             std::string cfProxy;
49             std::string cfSubDb;
50             std::string database;
51             std::string target;
52             std::string query_encoding;
53             std::string sru;
54             std::string request_syntax;
55             std::string element_set;
56             std::string record_encoding;
57             std::string transform_xsl_fname;
58             bool use_turbomarc;
59             bool piggyback;
60             CCL_bibset ccl_bibset;
61             Searchable();
62             ~Searchable();
63         };
64         class Zoom::Backend : boost::noncopyable {
65             friend class Impl;
66             friend class Frontend;
67             std::string zurl;
68             ZOOM_connection m_connection;
69             ZOOM_resultset m_resultset;
70             std::string m_frontend_database;
71             SearchablePtr sptr;
72             xsltStylesheetPtr xsp;
73         public:
74             Backend(SearchablePtr sptr);
75             ~Backend();
76             void connect(std::string zurl, int *error, const char **addinfo);
77             void search_pqf(const char *pqf, Odr_int *hits,
78                             int *error, const char **addinfo);
79             void present(Odr_int start, Odr_int number, ZOOM_record *recs,
80                          int *error, const char **addinfo);
81             void set_option(const char *name, const char *value);
82             int get_error(const char **addinfo);
83         };
84         class Zoom::Frontend : boost::noncopyable {
85             friend class Impl;
86             Impl *m_p;
87             bool m_is_virtual;
88             bool m_in_use;
89             yazpp_1::GDU m_init_gdu;
90             BackendPtr m_backend;
91             void handle_package(mp::Package &package);
92             void handle_search(mp::Package &package);
93             void handle_present(mp::Package &package);
94             BackendPtr get_backend_from_databases(std::string &database,
95                                                   int *error,
96                                                   const char **addinfo);
97             Z_Records *get_records(Odr_int start,
98                                    Odr_int number_to_present,
99                                    int *error,
100                                    const char **addinfo,
101                                    Odr_int *number_of_records_returned,
102                                    ODR odr, BackendPtr b,
103                                    Odr_oid *preferredRecordSyntax,
104                                    const char *element_set_name);
105         public:
106             Frontend(Impl *impl);
107             ~Frontend();
108         };
109         class Zoom::Impl {
110             friend class Frontend;
111         public:
112             Impl();
113             ~Impl();
114             void process(metaproxy_1::Package & package);
115             void configure(const xmlNode * ptr, bool test_only);
116         private:
117             FrontendPtr get_frontend(mp::Package &package);
118             void release_frontend(mp::Package &package);
119             SearchablePtr parse_torus(const xmlNode *ptr);
120             struct cql_node *convert_cql_fields(struct cql_node *cn, ODR odr);
121             std::map<mp::Session, FrontendPtr> m_clients;            
122             boost::mutex m_mutex;
123             boost::condition m_cond_session_ready;
124             std::string torus_url;
125             std::map<std::string,std::string> fieldmap;
126             std::string xsldir;
127         };
128     }
129 }
130
131 // define Pimpl wrapper forwarding to Impl
132  
133 yf::Zoom::Zoom() : m_p(new Impl)
134 {
135 }
136
137 yf::Zoom::~Zoom()
138 {  // must have a destructor because of boost::scoped_ptr
139 }
140
141 void yf::Zoom::configure(const xmlNode *xmlnode, bool test_only)
142 {
143     m_p->configure(xmlnode, test_only);
144 }
145
146 void yf::Zoom::process(mp::Package &package) const
147 {
148     m_p->process(package);
149 }
150
151
152 // define Implementation stuff
153
154 yf::Zoom::Backend::Backend(SearchablePtr ptr) : sptr(ptr)
155 {
156     m_connection = ZOOM_connection_create(0);
157     m_resultset = 0;
158     xsp = 0;
159 }
160
161 yf::Zoom::Backend::~Backend()
162 {
163     if (xsp)
164         xsltFreeStylesheet(xsp);
165     ZOOM_connection_destroy(m_connection);
166     ZOOM_resultset_destroy(m_resultset);
167 }
168
169 void yf::Zoom::Backend::connect(std::string zurl,
170                                 int *error, const char **addinfo)
171 {
172     ZOOM_connection_connect(m_connection, zurl.c_str(), 0);
173     *error = ZOOM_connection_error(m_connection, 0, addinfo);
174 }
175
176 void yf::Zoom::Backend::search_pqf(const char *pqf, Odr_int *hits,
177                                    int *error, const char **addinfo)
178 {
179     m_resultset = ZOOM_connection_search_pqf(m_connection, pqf);
180     *error = ZOOM_connection_error(m_connection, 0, addinfo);
181     if (*error == 0)
182         *hits = ZOOM_resultset_size(m_resultset);
183     else
184         *hits = 0;
185 }
186
187 void yf::Zoom::Backend::present(Odr_int start, Odr_int number,
188                                 ZOOM_record *recs,
189                                 int *error, const char **addinfo)
190 {
191     ZOOM_resultset_records(m_resultset, recs, start, number);
192     *error = ZOOM_connection_error(m_connection, 0, addinfo);
193 }
194
195 void yf::Zoom::Backend::set_option(const char *name, const char *value)
196 {
197     ZOOM_connection_option_set(m_connection, name, value);
198     if (m_resultset)
199         ZOOM_resultset_option_set(m_resultset, name, value);
200 }
201
202 int yf::Zoom::Backend::get_error(const char **addinfo)
203 {
204     return ZOOM_connection_error(m_connection, 0, addinfo);
205 }
206
207 yf::Zoom::Searchable::Searchable()
208 {
209     piggyback = true;
210     use_turbomarc = true;
211     ccl_bibset = ccl_qual_mk();
212 }
213
214 yf::Zoom::Searchable::~Searchable()
215 {
216     ccl_qual_rm(&ccl_bibset);
217 }
218
219 yf::Zoom::Frontend::Frontend(Impl *impl) : 
220     m_p(impl), m_is_virtual(false), m_in_use(true)
221 {
222 }
223
224 yf::Zoom::Frontend::~Frontend()
225 {
226 }
227
228 yf::Zoom::FrontendPtr yf::Zoom::Impl::get_frontend(mp::Package &package)
229 {
230     boost::mutex::scoped_lock lock(m_mutex);
231
232     std::map<mp::Session,yf::Zoom::FrontendPtr>::iterator it;
233     
234     while(true)
235     {
236         it = m_clients.find(package.session());
237         if (it == m_clients.end())
238             break;
239         
240         if (!it->second->m_in_use)
241         {
242             it->second->m_in_use = true;
243             return it->second;
244         }
245         m_cond_session_ready.wait(lock);
246     }
247     FrontendPtr f(new Frontend(this));
248     m_clients[package.session()] = f;
249     f->m_in_use = true;
250     return f;
251 }
252
253 void yf::Zoom::Impl::release_frontend(mp::Package &package)
254 {
255     boost::mutex::scoped_lock lock(m_mutex);
256     std::map<mp::Session,yf::Zoom::FrontendPtr>::iterator it;
257     
258     it = m_clients.find(package.session());
259     if (it != m_clients.end())
260     {
261         if (package.session().is_closed())
262         {
263             m_clients.erase(it);
264         }
265         else
266         {
267             it->second->m_in_use = false;
268         }
269         m_cond_session_ready.notify_all();
270     }
271 }
272
273 yf::Zoom::Impl::Impl()
274 {
275 }
276
277 yf::Zoom::Impl::~Impl()
278
279 }
280
281 yf::Zoom::SearchablePtr yf::Zoom::Impl::parse_torus(const xmlNode *ptr1)
282 {
283     SearchablePtr notfound;
284     if (!ptr1)
285         return notfound;
286     for (ptr1 = ptr1->children; ptr1; ptr1 = ptr1->next)
287     {
288         if (ptr1->type != XML_ELEMENT_NODE)
289             continue;
290         if (!strcmp((const char *) ptr1->name, "record"))
291         {
292             const xmlNode *ptr2 = ptr1;
293             for (ptr2 = ptr2->children; ptr2; ptr2 = ptr2->next)
294             {
295                 if (ptr2->type != XML_ELEMENT_NODE)
296                     continue;
297                 if (!strcmp((const char *) ptr2->name, "layer"))
298                 {
299                     Zoom::SearchablePtr s(new Searchable);
300
301                     const xmlNode *ptr3 = ptr2;
302                     for (ptr3 = ptr3->children; ptr3; ptr3 = ptr3->next)
303                     {
304                         if (ptr3->type != XML_ELEMENT_NODE)
305                             continue;
306                         if (!strcmp((const char *) ptr3->name,
307                                     "authentication"))
308                         {
309                             s->authentication = mp::xml::get_text(ptr3);
310                         }
311                         else if (!strcmp((const char *) ptr3->name,
312                                     "cfAuth"))
313                         {
314                             s->cfAuth = mp::xml::get_text(ptr3);
315                         } 
316                         else if (!strcmp((const char *) ptr3->name,
317                                     "cfProxy"))
318                         {
319                             s->cfProxy = mp::xml::get_text(ptr3);
320                         }  
321                         else if (!strcmp((const char *) ptr3->name,
322                                     "cfSubDb"))
323                         {
324                             s->cfSubDb = mp::xml::get_text(ptr3);
325                         }  
326                         else if (!strcmp((const char *) ptr3->name, "id"))
327                         {
328                             s->database = mp::xml::get_text(ptr3);
329                         }
330                         else if (!strcmp((const char *) ptr3->name, "zurl"))
331                         {
332                             s->target = mp::xml::get_text(ptr3);
333                         }
334                         else if (!strcmp((const char *) ptr3->name, "sru"))
335                         {
336                             s->sru = mp::xml::get_text(ptr3);
337                         }
338                         else if (!strcmp((const char *) ptr3->name,
339                                          "queryEncoding"))
340                         {
341                             s->query_encoding = mp::xml::get_text(ptr3);
342                         }
343                         else if (!strcmp((const char *) ptr3->name,
344                                          "piggyback"))
345                         {
346                             s->piggyback = mp::xml::get_bool(ptr3, true);
347                         }
348                         else if (!strcmp((const char *) ptr3->name,
349                                          "requestSyntax"))
350                         {
351                             s->request_syntax = mp::xml::get_text(ptr3);
352                         }
353                         else if (!strcmp((const char *) ptr3->name,
354                                          "elementSet"))
355                         {
356                             s->element_set = mp::xml::get_text(ptr3);
357                         }
358                         else if (!strcmp((const char *) ptr3->name,
359                                          "recordEncoding"))
360                         {
361                             s->record_encoding = mp::xml::get_text(ptr3);
362                         }
363                         else if (!strcmp((const char *) ptr3->name,
364                                          "transform"))
365                         {
366                             s->transform_xsl_fname = mp::xml::get_text(ptr3);
367                         }
368                         else if (!strcmp((const char *) ptr3->name,
369                                          "useTurboMarc"))
370                         {
371                             ; // useTurboMarc is ignored
372                         }
373                         else if (!strncmp((const char *) ptr3->name,
374                                           "cclmap_", 7))
375                         {
376                             std::string value = mp::xml::get_text(ptr3);
377                             ccl_qual_fitem(s->ccl_bibset, value.c_str(),
378                                            (const char *) ptr3->name + 7);
379                         }
380                     }
381                     return s;
382                 }
383             }
384         }
385     }
386     return notfound;
387 }
388
389 void yf::Zoom::Impl::configure(const xmlNode *ptr, bool test_only)
390 {
391     for (ptr = ptr->children; ptr; ptr = ptr->next)
392     {
393         if (ptr->type != XML_ELEMENT_NODE)
394             continue;
395         else if (!strcmp((const char *) ptr->name, "torus"))
396         {
397             const struct _xmlAttr *attr;
398             for (attr = ptr->properties; attr; attr = attr->next)
399             {
400                 if (!strcmp((const char *) attr->name, "url"))
401                     torus_url = mp::xml::get_text(attr->children);
402                 else if (!strcmp((const char *) attr->name, "xsldir"))
403                     xsldir = mp::xml::get_text(attr->children);
404                 else
405                     throw mp::filter::FilterException(
406                         "Bad attribute " + std::string((const char *)
407                                                        attr->name));
408             }
409         }
410         else if (!strcmp((const char *) ptr->name, "fieldmap"))
411         {
412             const struct _xmlAttr *attr;
413             std::string ccl_field;
414             std::string cql_field;
415             for (attr = ptr->properties; attr; attr = attr->next)
416             {
417                 if (!strcmp((const char *) attr->name, "ccl"))
418                     ccl_field = mp::xml::get_text(attr->children);
419                 else if (!strcmp((const char *) attr->name, "cql"))
420                     cql_field = mp::xml::get_text(attr->children);
421                 else
422                     throw mp::filter::FilterException(
423                         "Bad attribute " + std::string((const char *)
424                                                        attr->name));
425             }
426             if (ccl_field.length() && cql_field.length())
427                 fieldmap[cql_field] = ccl_field;
428         }
429         else if (!strcmp((const char *) ptr->name, "records"))
430         {
431             yaz_log(YLOG_WARN, "records ignored!");
432         }
433         else
434         {
435             throw mp::filter::FilterException
436                 ("Bad element " 
437                  + std::string((const char *) ptr->name)
438                  + " in zoom filter");
439         }
440     }
441 }
442
443 yf::Zoom::BackendPtr yf::Zoom::Frontend::get_backend_from_databases(
444     std::string &database, int *error, const char **addinfo)
445 {
446     std::list<BackendPtr>::const_iterator map_it;
447     if (m_backend && m_backend->m_frontend_database == database)
448         return m_backend;
449
450     xmlDoc *doc = mp::get_searchable(m_p->torus_url, database);
451     if (!doc)
452     {
453         *error = YAZ_BIB1_DATABASE_DOES_NOT_EXIST;
454         *addinfo = database.c_str();
455         BackendPtr b;
456         return b;
457     }
458     SearchablePtr sptr = m_p->parse_torus(xmlDocGetRootElement(doc));
459     xmlFreeDoc(doc);
460     if (!sptr)
461     {
462         *error = YAZ_BIB1_DATABASE_DOES_NOT_EXIST;
463         *addinfo = database.c_str();
464         BackendPtr b;
465         return b;
466     }
467         
468     xsltStylesheetPtr xsp = 0;
469     if (sptr->transform_xsl_fname.length())
470     {
471         std::string fname;
472
473         if (m_p->xsldir.length()) 
474             fname = m_p->xsldir + "/" + sptr->transform_xsl_fname;
475         else
476             fname = sptr->transform_xsl_fname;
477         xmlDoc *xsp_doc = xmlParseFile(fname.c_str());
478         if (!xsp_doc)
479         {
480             *error = YAZ_BIB1_TEMPORARY_SYSTEM_ERROR;
481             *addinfo = "xmlParseFile failed";
482             BackendPtr b;
483             return b;
484         }
485         xsp = xsltParseStylesheetDoc(xsp_doc);
486         if (!xsp)
487         {
488             *error = YAZ_BIB1_DATABASE_DOES_NOT_EXIST;
489             *addinfo = "xsltParseStylesheetDoc failed";
490             BackendPtr b;
491             xmlFreeDoc(xsp_doc);
492             return b;
493         }
494     }
495
496     m_backend.reset();
497
498     BackendPtr b(new Backend(sptr));
499
500     std::string cf_parm;
501     b->xsp = xsp;
502     b->m_frontend_database = database;
503     std::string authentication = sptr->authentication;
504
505     if (sptr->query_encoding.length())
506         b->set_option("rpnCharset", sptr->query_encoding.c_str());
507
508     if (sptr->cfAuth.length())
509     {
510         b->set_option("user", sptr->cfAuth.c_str());
511         if (authentication.length())
512         {
513             size_t found = authentication.find('/');
514             if (found != std::string::npos)
515             {
516                 cf_parm += "user=" + mp::util::uri_encode(authentication.substr(0, found))
517                     + "&password=" + mp::util::uri_encode(authentication.substr(found+1));
518             }
519             else
520                 cf_parm += "user=" + mp::util::uri_encode(authentication);
521         }
522     }
523     else if (authentication.length())
524         b->set_option("user", authentication.c_str());
525
526     if (sptr->cfProxy.length())
527     {
528         if (cf_parm.length())
529             cf_parm += "&";
530         cf_parm += "proxy=" + mp::util::uri_encode(sptr->cfProxy);
531     }
532     if (sptr->cfSubDb.length())
533     {
534         if (cf_parm.length())
535             cf_parm += "&";
536         cf_parm += "subdatabase=" + mp::util::uri_encode(sptr->cfSubDb);
537     }
538
539     std::string url;
540     if (sptr->sru.length())
541     {
542         url = "http://" + sptr->target;
543         b->set_option("sru", sptr->sru.c_str());
544     }
545     else
546     {
547         url = sptr->target;
548     }
549     if (cf_parm.length())
550     {
551         url += "," + cf_parm;
552     }
553     b->connect(url, error, addinfo);
554     if (*error == 0)
555     {
556         m_backend = b;
557     }
558     return b;
559 }
560
561 Z_Records *yf::Zoom::Frontend::get_records(Odr_int start,
562                                            Odr_int number_to_present,
563                                            int *error,
564                                            const char **addinfo,
565                                            Odr_int *number_of_records_returned,
566                                            ODR odr,
567                                            BackendPtr b,
568                                            Odr_oid *preferredRecordSyntax,
569                                            const char *element_set_name)
570 {
571     *number_of_records_returned = 0;
572     Z_Records *records = 0;
573     bool enable_pz2_transform = false;
574
575     if (start < 0 || number_to_present <= 0)
576         return records;
577     
578     if (number_to_present > 10000)
579         number_to_present = 10000;
580     
581     ZOOM_record *recs = (ZOOM_record *)
582         odr_malloc(odr, number_to_present * sizeof(*recs));
583
584     char oid_name_str[OID_STR_MAX];
585     const char *syntax_name = 0;
586
587     if (preferredRecordSyntax)
588     {
589         if (!oid_oidcmp(preferredRecordSyntax, yaz_oid_recsyn_xml)
590             && element_set_name &&
591             !strcmp(element_set_name, "pz2"))
592         {
593             if (b->sptr->request_syntax.length())
594             {
595                 syntax_name = b->sptr->request_syntax.c_str();
596                 enable_pz2_transform = true;
597             }
598         }
599         else
600         {
601             syntax_name =
602                 yaz_oid_to_string_buf(preferredRecordSyntax, 0, oid_name_str);
603         }
604     }
605
606     b->set_option("preferredRecordSyntax", syntax_name);
607
608     if (enable_pz2_transform)
609     {
610         element_set_name = "F";
611         if (b->sptr->element_set.length())
612             element_set_name = b->sptr->element_set.c_str();
613     }
614
615     b->set_option("elementSetName", element_set_name);
616
617     b->present(start, number_to_present, recs, error, addinfo);
618
619     Odr_int i = 0;
620     if (!*error)
621     {
622         for (i = 0; i < number_to_present; i++)
623             if (!recs[i])
624                 break;
625     }
626     if (i > 0)
627     {  // only return records if no error and at least one record
628         char *odr_database = odr_strdup(odr,
629                                         b->m_frontend_database.c_str());
630         Z_NamePlusRecordList *npl = (Z_NamePlusRecordList *)
631             odr_malloc(odr, sizeof(*npl));
632         *number_of_records_returned = i;
633         npl->num_records = i;
634         npl->records = (Z_NamePlusRecord **)
635             odr_malloc(odr, i * sizeof(*npl->records));
636         for (i = 0; i < number_to_present; i++)
637         {
638             Z_NamePlusRecord *npr = 0;
639             const char *addinfo;
640             int sur_error = ZOOM_record_error(recs[i], 0 /* msg */,
641                                               &addinfo, 0 /* diagset */);
642                 
643             if (sur_error)
644             {
645                 npr = zget_surrogateDiagRec(odr, odr_database, sur_error,
646                                             addinfo);
647             }
648             else if (enable_pz2_transform)
649             {
650                 char rec_type_str[100];
651
652                 strcpy(rec_type_str, b->sptr->use_turbomarc ?
653                        "txml" : "xml");
654                 
655                 // prevent buffer overflow ...
656                 if (b->sptr->record_encoding.length() > 0 &&
657                     b->sptr->record_encoding.length() < 
658                     (sizeof(rec_type_str)-20))
659                 {
660                     strcat(rec_type_str, "; charset=");
661                     strcat(rec_type_str, b->sptr->record_encoding.c_str());
662                 }
663                 
664                 int rec_len;
665                 const char *rec_buf = ZOOM_record_get(recs[i], rec_type_str,
666                                                       &rec_len);
667                 if (rec_buf && b->xsp)
668                 {
669                     xmlDoc *rec_doc = xmlParseMemory(rec_buf, rec_len);
670                     if (rec_doc)
671                     { 
672                         xmlDoc *rec_res;
673                         rec_res = xsltApplyStylesheet(b->xsp, rec_doc, 0);
674
675                         if (rec_res)
676                             xsltSaveResultToString((xmlChar **) &rec_buf, &rec_len,
677                                                    rec_res, b->xsp);
678                     }
679                 }
680
681                 if (rec_buf)
682                 {
683                     npr = (Z_NamePlusRecord *) odr_malloc(odr, sizeof(*npr));
684                     npr->databaseName = odr_database;
685                     npr->which = Z_NamePlusRecord_databaseRecord;
686                     npr->u.databaseRecord =
687                         z_ext_record_xml(odr, rec_buf, rec_len);
688                 }
689                 else
690                 {
691                     npr = zget_surrogateDiagRec(
692                         odr, odr_database, 
693                         YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS,
694                         rec_type_str);
695                 }
696             }
697             else
698             {
699                 Z_External *ext =
700                     (Z_External *) ZOOM_record_get(recs[i], "ext", 0);
701                 if (ext)
702                 {
703                     npr = (Z_NamePlusRecord *) odr_malloc(odr, sizeof(*npr));
704                     npr->databaseName = odr_database;
705                     npr->which = Z_NamePlusRecord_databaseRecord;
706                     npr->u.databaseRecord = ext;
707                 }
708                 else
709                 {
710                     npr = zget_surrogateDiagRec(
711                         odr, odr_database, 
712                         YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS,
713                         "ZOOM_record, type ext");
714                 }
715             }
716             npl->records[i] = npr;
717         }
718         records = (Z_Records*) odr_malloc(odr, sizeof(*records));
719         records->which = Z_Records_DBOSD;
720         records->u.databaseOrSurDiagnostics = npl;
721     }
722     return records;
723 }
724     
725 struct cql_node *yf::Zoom::Impl::convert_cql_fields(struct cql_node *cn,
726                                                     ODR odr)
727 {
728     struct cql_node *r = 0;
729     if (!cn)
730         return 0;
731     switch (cn->which)
732     {
733     case CQL_NODE_ST:
734         if (cn->u.st.index)
735         {
736             std::map<std::string,std::string>::const_iterator it;
737             it = fieldmap.find(cn->u.st.index);
738             if (it == fieldmap.end())
739                 return cn;
740             cn->u.st.index = odr_strdup(odr, it->second.c_str());
741         }
742         break;
743     case CQL_NODE_BOOL:
744         r = convert_cql_fields(cn->u.boolean.left, odr);
745         if (!r)
746             r = convert_cql_fields(cn->u.boolean.right, odr);
747         break;
748     case CQL_NODE_SORT:
749         r = convert_cql_fields(cn->u.sort.search, odr);
750         break;
751     }
752     return r;
753 }
754
755 void yf::Zoom::Frontend::handle_search(mp::Package &package)
756 {
757     Z_GDU *gdu = package.request().get();
758     Z_APDU *apdu_req = gdu->u.z3950;
759     Z_APDU *apdu_res = 0;
760     mp::odr odr;
761     Z_SearchRequest *sr = apdu_req->u.searchRequest;
762     if (sr->num_databaseNames != 1)
763     {
764         apdu_res = odr.create_searchResponse(
765             apdu_req, YAZ_BIB1_TOO_MANY_DATABASES_SPECIFIED, 0);
766         package.response() = apdu_res;
767         return;
768     }
769
770     int error = 0;
771     const char *addinfo = 0;
772     std::string db(sr->databaseNames[0]);
773     BackendPtr b = get_backend_from_databases(db, &error, &addinfo);
774     if (error)
775     {
776         apdu_res = 
777             odr.create_searchResponse(
778                 apdu_req, error, addinfo);
779         package.response() = apdu_res;
780         return;
781     }
782
783     b->set_option("setname", "default");
784
785     Odr_int hits = 0;
786     Z_Query *query = sr->query;
787     WRBUF ccl_wrbuf = 0;
788     WRBUF pqf_wrbuf = 0;
789
790     if (query->which == Z_Query_type_1 || query->which == Z_Query_type_101)
791     {
792         // RPN
793         pqf_wrbuf = wrbuf_alloc();
794         yaz_rpnquery_to_wrbuf(pqf_wrbuf, query->u.type_1);
795     }
796     else if (query->which == Z_Query_type_2)
797     {
798         // CCL
799         ccl_wrbuf = wrbuf_alloc();
800         wrbuf_write(ccl_wrbuf, (const char *) query->u.type_2->buf,
801                     query->u.type_2->len);
802     }
803     else if (query->which == Z_Query_type_104 &&
804              query->u.type_104->which == Z_External_CQL)
805     {
806         // CQL
807         const char *cql = query->u.type_104->u.cql;
808         CQL_parser cp = cql_parser_create();
809         int r = cql_parser_string(cp, cql);
810         if (r)
811         {
812             cql_parser_destroy(cp);
813             apdu_res = 
814                 odr.create_searchResponse(apdu_req, 
815                                           YAZ_BIB1_MALFORMED_QUERY,
816                                           "CQL syntax error");
817             package.response() = apdu_res;
818             return;
819         }
820         struct cql_node *cn = cql_parser_result(cp);
821         struct cql_node *cn_error = m_p->convert_cql_fields(cn, odr);
822         if (cn_error)
823         {
824             // hopefully we are getting a ptr to a index+relation+term node
825             addinfo = 0;
826             if (cn_error->which == CQL_NODE_ST)
827                 addinfo = cn_error->u.st.index;
828
829             apdu_res = 
830                 odr.create_searchResponse(apdu_req, 
831                                           YAZ_BIB1_UNSUPP_USE_ATTRIBUTE,
832                                           addinfo);
833             package.response() = apdu_res;
834             return;
835         }
836         char ccl_buf[1024];
837
838         r = cql_to_ccl_buf(cn, ccl_buf, sizeof(ccl_buf));
839         if (r == 0)
840         {
841             ccl_wrbuf = wrbuf_alloc();
842             wrbuf_puts(ccl_wrbuf, ccl_buf);
843         }
844         cql_parser_destroy(cp);
845         if (r)
846         {
847             apdu_res = 
848                 odr.create_searchResponse(apdu_req, 
849                                           YAZ_BIB1_MALFORMED_QUERY,
850                                           "CQL to CCL conversion error");
851             package.response() = apdu_res;
852             return;
853         }
854     }
855     else
856     {
857         apdu_res = 
858             odr.create_searchResponse(apdu_req, YAZ_BIB1_QUERY_TYPE_UNSUPP, 0);
859         package.response() = apdu_res;
860         return;
861     }
862
863     if (ccl_wrbuf)
864     {
865         // CCL to PQF
866         assert(pqf_wrbuf == 0);
867         int cerror, cpos;
868         struct ccl_rpn_node *cn;
869         cn = ccl_find_str(b->sptr->ccl_bibset, wrbuf_cstr(ccl_wrbuf),
870                           &cerror, &cpos);
871         wrbuf_destroy(ccl_wrbuf);
872         if (!cn)
873         {
874             char *addinfo = odr_strdup(odr, ccl_err_msg(cerror));
875             int z3950_diag = YAZ_BIB1_MALFORMED_QUERY;
876
877             switch (cerror)
878             {
879             case CCL_ERR_UNKNOWN_QUAL:
880                 z3950_diag = YAZ_BIB1_UNSUPP_USE_ATTRIBUTE;
881                 break;
882             case CCL_ERR_TRUNC_NOT_LEFT: 
883             case CCL_ERR_TRUNC_NOT_RIGHT:
884             case CCL_ERR_TRUNC_NOT_BOTH:
885                 z3950_diag = YAZ_BIB1_UNSUPP_TRUNCATION_ATTRIBUTE;
886                 break;
887             }
888             apdu_res = 
889                 odr.create_searchResponse(apdu_req, z3950_diag, addinfo);
890             package.response() = apdu_res;
891             return;
892         }
893         pqf_wrbuf = wrbuf_alloc();
894         ccl_pquery(pqf_wrbuf, cn);
895         ccl_rpn_delete(cn);
896     }
897     
898     assert(pqf_wrbuf);
899     b->search_pqf(wrbuf_cstr(pqf_wrbuf), &hits, &error, &addinfo);
900     
901     wrbuf_destroy(pqf_wrbuf);
902     
903     const char *element_set_name = 0;
904     Odr_int number_to_present = 0;
905     if (!error)
906         mp::util::piggyback_sr(sr, hits, number_to_present, &element_set_name);
907     
908     Odr_int number_of_records_returned = 0;
909     Z_Records *records = get_records(
910         0, number_to_present, &error, &addinfo,
911         &number_of_records_returned, odr, b, sr->preferredRecordSyntax,
912         element_set_name);
913     apdu_res = odr.create_searchResponse(apdu_req, error, addinfo);
914     if (records)
915     {
916         apdu_res->u.searchResponse->records = records;
917         apdu_res->u.searchResponse->numberOfRecordsReturned =
918             odr_intdup(odr, number_of_records_returned);
919     }
920     apdu_res->u.searchResponse->resultCount = odr_intdup(odr, hits);
921     package.response() = apdu_res;
922 }
923
924 void yf::Zoom::Frontend::handle_present(mp::Package &package)
925 {
926     Z_GDU *gdu = package.request().get();
927     Z_APDU *apdu_req = gdu->u.z3950;
928     Z_APDU *apdu_res = 0;
929     Z_PresentRequest *pr = apdu_req->u.presentRequest;
930
931     mp::odr odr;
932     if (!m_backend)
933     {
934         package.response() = odr.create_presentResponse(
935             apdu_req, YAZ_BIB1_SPECIFIED_RESULT_SET_DOES_NOT_EXIST, 0);
936         return;
937     }
938     const char *element_set_name = 0;
939     Z_RecordComposition *comp = pr->recordComposition;
940     if (comp && comp->which != Z_RecordComp_simple)
941     {
942         package.response() = odr.create_presentResponse(
943             apdu_req, 
944             YAZ_BIB1_PRESENT_COMP_SPEC_PARAMETER_UNSUPP, 0);
945         return;
946     }
947     if (comp && comp->u.simple->which == Z_ElementSetNames_generic)
948         element_set_name = comp->u.simple->u.generic;
949     Odr_int number_of_records_returned = 0;
950     int error = 0;
951     const char *addinfo = 0;
952     Z_Records *records = get_records(
953         *pr->resultSetStartPoint - 1, *pr->numberOfRecordsRequested,
954         &error, &addinfo, &number_of_records_returned, odr, m_backend,
955         pr->preferredRecordSyntax, element_set_name);
956
957     apdu_res = odr.create_presentResponse(apdu_req, error, addinfo);
958     if (records)
959     {
960         apdu_res->u.presentResponse->records = records;
961         apdu_res->u.presentResponse->numberOfRecordsReturned =
962             odr_intdup(odr, number_of_records_returned);
963     }
964     package.response() = apdu_res;
965 }
966
967 void yf::Zoom::Frontend::handle_package(mp::Package &package)
968 {
969     Z_GDU *gdu = package.request().get();
970     if (!gdu)
971         ;
972     else if (gdu->which == Z_GDU_Z3950)
973     {
974         Z_APDU *apdu_req = gdu->u.z3950;
975         if (apdu_req->which == Z_APDU_initRequest)
976         {
977             mp::odr odr;
978             package.response() = odr.create_close(
979                 apdu_req,
980                 Z_Close_protocolError,
981                 "double init");
982         }
983         else if (apdu_req->which == Z_APDU_searchRequest)
984         {
985             handle_search(package);
986         }
987         else if (apdu_req->which == Z_APDU_presentRequest)
988         {
989             handle_present(package);
990         }
991         else
992         {
993             mp::odr odr;
994             package.response() = odr.create_close(
995                 apdu_req,
996                 Z_Close_protocolError,
997                 "zoom filter cannot handle this APDU");
998             package.session().close();
999         }
1000     }
1001     else
1002     {
1003         package.session().close();
1004     }
1005 }
1006
1007 void yf::Zoom::Impl::process(mp::Package &package)
1008 {
1009     FrontendPtr f = get_frontend(package);
1010     Z_GDU *gdu = package.request().get();
1011
1012     if (f->m_is_virtual)
1013     {
1014         f->handle_package(package);
1015     }
1016     else if (gdu && gdu->which == Z_GDU_Z3950 && gdu->u.z3950->which ==
1017              Z_APDU_initRequest)
1018     {
1019         Z_InitRequest *req = gdu->u.z3950->u.initRequest;
1020         f->m_init_gdu = gdu;
1021         
1022         mp::odr odr;
1023         Z_APDU *apdu = odr.create_initResponse(gdu->u.z3950, 0, 0);
1024         Z_InitResponse *resp = apdu->u.initResponse;
1025         
1026         int i;
1027         static const int masks[] = {
1028             Z_Options_search,
1029             Z_Options_present,
1030             -1 
1031         };
1032         for (i = 0; masks[i] != -1; i++)
1033             if (ODR_MASK_GET(req->options, masks[i]))
1034                 ODR_MASK_SET(resp->options, masks[i]);
1035         
1036         static const int versions[] = {
1037             Z_ProtocolVersion_1,
1038             Z_ProtocolVersion_2,
1039             Z_ProtocolVersion_3,
1040             -1
1041         };
1042         for (i = 0; versions[i] != -1; i++)
1043             if (ODR_MASK_GET(req->protocolVersion, versions[i]))
1044                 ODR_MASK_SET(resp->protocolVersion, versions[i]);
1045             else
1046                 break;
1047         
1048         *resp->preferredMessageSize = *req->preferredMessageSize;
1049         *resp->maximumRecordSize = *req->maximumRecordSize;
1050         
1051         package.response() = apdu;
1052         f->m_is_virtual = true;
1053     }
1054     else
1055         package.move();
1056
1057     release_frontend(package);
1058 }
1059
1060
1061 static mp::filter::Base* filter_creator()
1062 {
1063     return new mp::filter::Zoom;
1064 }
1065
1066 extern "C" {
1067     struct metaproxy_1_filter_struct metaproxy_1_filter_zoom = {
1068         0,
1069         "zoom",
1070         filter_creator
1071     };
1072 }
1073
1074
1075 /*
1076  * Local variables:
1077  * c-basic-offset: 4
1078  * c-file-style: "Stroustrup"
1079  * indent-tabs-mode: nil
1080  * End:
1081  * vim: shiftwidth=4 tabstop=8 expandtab
1082  */
1083