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