6cabaf90c82cc93fbde76f0791c0464d1c110d76
[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 <boost/thread/mutex.hpp>
27 #include <boost/thread/condition.hpp>
28 #include <yaz/diagbib1.h>
29 #include <yaz/log.h>
30 #include <yaz/zgdu.h>
31 #include <yaz/querytowrbuf.h>
32
33 namespace mp = metaproxy_1;
34 namespace yf = mp::filter;
35
36 namespace metaproxy_1 {
37     namespace filter {
38         class Zoom::Backend {
39             friend class Impl;
40             friend class Frontend;
41             std::string zurl;
42             ZOOM_connection m_connection;
43             ZOOM_resultset m_resultset;
44             std::string m_frontend_database;
45         public:
46             Backend();
47             ~Backend();
48             void connect(std::string zurl, int *error, const char **addinfo);
49             void search_pqf(const char *pqf, Odr_int *hits,
50                             int *error, const char **addinfo);
51             void set_option(const char *name, const char *value);
52             int get_error(const char **addinfo);
53         };
54         struct Zoom::Searchable {
55             std::string m_database;
56             std::string m_target;
57             std::string query_encoding;
58             std::string sru;
59             Searchable(std::string norm_db, std::string target);
60             ~Searchable();
61         };
62         class Zoom::Frontend {
63             friend class Impl;
64             Impl *m_p;
65             bool m_is_virtual;
66             bool m_in_use;
67             yazpp_1::GDU m_init_gdu;
68             std::list<BackendPtr> m_backend_list;
69             void handle_package(mp::Package &package);
70             void handle_search(mp::Package &package);
71             void handle_present(mp::Package &package);
72             BackendPtr get_backend_from_databases(std::string &database,
73                                                   int *error,
74                                                   const char **addinfo);
75         public:
76             Frontend(Impl *impl);
77             ~Frontend();
78         };
79         class Zoom::Impl {
80             friend class Frontend;
81         public:
82             Impl();
83             ~Impl();
84             void process(metaproxy_1::Package & package);
85             void configure(const xmlNode * ptr, bool test_only);
86         private:
87             FrontendPtr get_frontend(mp::Package &package);
88             void release_frontend(mp::Package &package);
89             void parse_torus(const xmlNode *ptr);
90
91             std::list<Zoom::Searchable>m_searchables;
92
93             std::map<mp::Session, FrontendPtr> m_clients;            
94             boost::mutex m_mutex;
95             boost::condition m_cond_session_ready;
96             mp::Torus torus;
97         };
98     }
99 }
100
101 // define Pimpl wrapper forwarding to Impl
102  
103 yf::Zoom::Zoom() : m_p(new Impl)
104 {
105 }
106
107 yf::Zoom::~Zoom()
108 {  // must have a destructor because of boost::scoped_ptr
109 }
110
111 void yf::Zoom::configure(const xmlNode *xmlnode, bool test_only)
112 {
113     m_p->configure(xmlnode, test_only);
114 }
115
116 void yf::Zoom::process(mp::Package &package) const
117 {
118     m_p->process(package);
119 }
120
121
122 // define Implementation stuff
123
124 yf::Zoom::Backend::Backend()
125 {
126     m_connection = ZOOM_connection_create(0);
127     m_resultset = 0;
128 }
129
130 yf::Zoom::Backend::~Backend()
131 {
132     ZOOM_connection_destroy(m_connection);
133     ZOOM_resultset_destroy(m_resultset);
134 }
135
136 void yf::Zoom::Backend::connect(std::string zurl,
137                                 int *error, const char **addinfo)
138 {
139     ZOOM_connection_connect(m_connection, zurl.c_str(), 0);
140     *error = ZOOM_connection_error(m_connection, 0, addinfo);
141     yaz_log(YLOG_LOG, "ZOOM_connection_connect: error: %d", *error);
142 }
143
144 void yf::Zoom::Backend::search_pqf(const char *pqf, Odr_int *hits,
145                                    int *error, const char **addinfo)
146 {
147     yaz_log(YLOG_LOG, "ZOOM_connection_search_pqf pqf=%s", pqf);
148     m_resultset = ZOOM_connection_search_pqf(m_connection, pqf);
149     *error = ZOOM_connection_error(m_connection, 0, addinfo);
150     yaz_log(YLOG_LOG, "ZOOM_connection_search_pqf: error: %d", *error);
151     if (*error == 0)
152         *hits = ZOOM_resultset_size(m_resultset);
153     else
154         *hits = 0;
155 }
156
157 void yf::Zoom::Backend::set_option(const char *name, const char *value)
158 {
159     ZOOM_connection_option_set(m_connection, name, value);
160 }
161
162 int yf::Zoom::Backend::get_error(const char **addinfo)
163 {
164     return ZOOM_connection_error(m_connection, 0, addinfo);
165 }
166
167 yf::Zoom::Searchable::Searchable(std::string database, 
168                                  std::string target)
169     : m_database(database), m_target(target)
170 {
171 }
172
173 yf::Zoom::Searchable::~Searchable()
174 {
175 }
176
177 yf::Zoom::Frontend::Frontend(Impl *impl) : 
178     m_p(impl), m_is_virtual(false), m_in_use(true)
179 {
180 }
181
182 yf::Zoom::Frontend::~Frontend()
183 {
184 }
185
186 yf::Zoom::FrontendPtr yf::Zoom::Impl::get_frontend(mp::Package &package)
187 {
188     boost::mutex::scoped_lock lock(m_mutex);
189
190     std::map<mp::Session,yf::Zoom::FrontendPtr>::iterator it;
191     
192     while(true)
193     {
194         it = m_clients.find(package.session());
195         if (it == m_clients.end())
196             break;
197         
198         if (!it->second->m_in_use)
199         {
200             it->second->m_in_use = true;
201             return it->second;
202         }
203         m_cond_session_ready.wait(lock);
204     }
205     FrontendPtr f(new Frontend(this));
206     m_clients[package.session()] = f;
207     f->m_in_use = true;
208     return f;
209 }
210
211 void yf::Zoom::Impl::release_frontend(mp::Package &package)
212 {
213     boost::mutex::scoped_lock lock(m_mutex);
214     std::map<mp::Session,yf::Zoom::FrontendPtr>::iterator it;
215     
216     it = m_clients.find(package.session());
217     if (it != m_clients.end())
218     {
219         if (package.session().is_closed())
220         {
221             m_clients.erase(it);
222         }
223         else
224         {
225             it->second->m_in_use = false;
226         }
227         m_cond_session_ready.notify_all();
228     }
229 }
230
231 yf::Zoom::Impl::Impl()
232 {
233 }
234
235 yf::Zoom::Impl::~Impl()
236
237 }
238
239 void yf::Zoom::Impl::parse_torus(const xmlNode *ptr1)
240 {
241     if (!ptr1)
242         return ;
243     for (ptr1 = ptr1->children; ptr1; ptr1 = ptr1->next)
244     {
245         if (ptr1->type != XML_ELEMENT_NODE)
246             continue;
247         if (!strcmp((const char *) ptr1->name, "record"))
248         {
249             const xmlNode *ptr2 = ptr1;
250             for (ptr2 = ptr2->children; ptr2; ptr2 = ptr2->next)
251             {
252                 if (ptr2->type != XML_ELEMENT_NODE)
253                     continue;
254                 if (!strcmp((const char *) ptr2->name, "layer"))
255                 {
256                     std::string database;
257                     std::string target;
258                     std::string route;
259                     std::string sru;
260                     std::string query_encoding;
261                     const xmlNode *ptr3 = ptr2;
262                     for (ptr3 = ptr3->children; ptr3; ptr3 = ptr3->next)
263                     {
264                         if (ptr3->type != XML_ELEMENT_NODE)
265                             continue;
266                         if (!strcmp((const char *) ptr3->name, "id"))
267                         {
268                             database = mp::xml::get_text(ptr3);
269                         }
270                         else if (!strcmp((const char *) ptr3->name, "zurl"))
271                         {
272                             target = mp::xml::get_text(ptr3);
273                         }
274                         else if (!strcmp((const char *) ptr3->name, "sru"))
275                         {
276                             sru = mp::xml::get_text(ptr3);
277                         }
278                         else if (!strcmp((const char *) ptr3->name,
279                                          "queryEncoding"))
280                         {
281                             query_encoding = mp::xml::get_text(ptr3);
282                         }
283                     }
284                     if (database.length() && target.length())
285                     {
286                         yaz_log(YLOG_LOG, "add db=%s target=%s", 
287                                 database.c_str(), target.c_str());
288                         Zoom::Searchable searchable(
289                             mp::util::database_name_normalize(database),
290                             target);
291                         searchable.query_encoding = query_encoding;
292                         searchable.sru = sru;
293                         m_searchables.push_back(searchable);
294                     }
295                 }
296             }
297         }
298     }
299 }
300
301
302 void yf::Zoom::Impl::configure(const xmlNode *ptr, bool test_only)
303 {
304     for (ptr = ptr->children; ptr; ptr = ptr->next)
305     {
306         if (ptr->type != XML_ELEMENT_NODE)
307             continue;
308         if (!strcmp((const char *) ptr->name, "records"))
309         {
310             parse_torus(ptr);
311         }
312         else if (!strcmp((const char *) ptr->name, "torus"))
313         {
314             std::string url;
315             const struct _xmlAttr *attr;
316             for (attr = ptr->properties; attr; attr = attr->next)
317             {
318                 if (!strcmp((const char *) attr->name, "url"))
319                     url = mp::xml::get_text(attr->children);
320                 else
321                     throw mp::filter::FilterException(
322                         "Bad attribute " + std::string((const char *)
323                                                        attr->name));
324             }
325             torus.read_searchables(url);
326             xmlDoc *doc = torus.get_doc();
327             if (doc)
328             {
329                 xmlNode *ptr = xmlDocGetRootElement(doc);
330                 parse_torus(ptr);
331             }
332         }
333         else
334         {
335             throw mp::filter::FilterException
336                 ("Bad element " 
337                  + std::string((const char *) ptr->name)
338                  + " in zoom filter");
339         }
340     }
341 }
342
343 yf::Zoom::BackendPtr yf::Zoom::Frontend::get_backend_from_databases(
344     std::string &database, int *error, const char **addinfo)
345 {
346     std::list<BackendPtr>::const_iterator map_it;
347     map_it = m_backend_list.begin();
348     for (; map_it != m_backend_list.end(); map_it++)
349         if ((*map_it)->m_frontend_database == database)
350             return *map_it;
351
352     std::list<Zoom::Searchable>::const_iterator map_s =
353         m_p->m_searchables.begin();
354
355     std::string c_db = mp::util::database_name_normalize(database);
356
357     while (map_s != m_p->m_searchables.end())
358     {
359         yaz_log(YLOG_LOG, "consider db=%s map db=%s",
360                 database.c_str(), map_s->m_database.c_str());
361         if (c_db.compare(map_s->m_database) == 0)
362             break;
363         map_s++;
364     }
365     if (map_s == m_p->m_searchables.end())
366     {
367         *error = YAZ_BIB1_DATABASE_DOES_NOT_EXIST;
368         *addinfo = database.c_str();
369         BackendPtr b;
370         return b;
371     }
372     BackendPtr b(new Backend);
373
374     if (map_s->query_encoding.length())
375         b->set_option("rpnCharset", map_s->query_encoding.c_str());
376
377     std::string url;
378     if (map_s->sru.length())
379     {
380         url = "http://" + map_s->m_target;
381         b->set_option("sru", map_s->sru.c_str());
382     }
383     else
384         url = map_s->m_target;
385
386     b->connect(url, error, addinfo);
387     if (*error == 0)
388     {
389         m_backend_list.push_back(b);
390     }
391     return b;
392 }
393
394 void yf::Zoom::Frontend::handle_search(mp::Package &package)
395 {
396     Z_GDU *gdu = package.request().get();
397     Z_APDU *apdu_req = gdu->u.z3950;
398     Z_APDU *apdu_res = 0;
399     mp::odr odr;
400     Z_SearchRequest *sr = apdu_req->u.searchRequest;
401     if (sr->num_databaseNames != 1)
402     {
403         apdu_res = odr.create_searchResponse(
404             apdu_req, YAZ_BIB1_TOO_MANY_DATABASES_SPECIFIED, 0);
405         package.response() = apdu_res;
406         return;
407     }
408
409     int error;
410     const char *addinfo;
411     std::string db(sr->databaseNames[0]);
412     BackendPtr b = get_backend_from_databases(db, &error, &addinfo);
413     if (error)
414     {
415         apdu_res = 
416             odr.create_searchResponse(
417                 apdu_req, error, addinfo);
418         package.response() = apdu_res;
419         return;
420     }
421     Z_Query *query = sr->query;
422     if (query->which == Z_Query_type_1 || query->which == Z_Query_type_101)
423     {
424         WRBUF w = wrbuf_alloc();
425         yaz_rpnquery_to_wrbuf(w, query->u.type_1);
426         Odr_int hits;
427         int error;
428         const char *addinfo = 0;
429
430         b->search_pqf(wrbuf_cstr(w), &hits, &error, &addinfo);
431         wrbuf_destroy(w);
432
433         apdu_res = 
434             odr.create_searchResponse(
435                 apdu_req, error, addinfo);
436         apdu_res->u.searchResponse->resultCount = odr_intdup(odr, hits);
437         package.response() = apdu_res;
438     }
439     else
440     {
441         apdu_res = 
442             odr.create_searchResponse(
443                 apdu_req,
444                 YAZ_BIB1_QUERY_TYPE_UNSUPP, 0);
445         package.response() = apdu_res;
446         return;
447     }
448 }
449
450 void yf::Zoom::Frontend::handle_present(mp::Package &package)
451 {
452     Z_GDU *gdu = package.request().get();
453     Z_APDU *apdu_req = gdu->u.z3950;
454     mp::odr odr;
455     package.response() = odr.create_close(
456         apdu_req,
457         Z_Close_protocolError,
458         "zoom filter has not implemented present request yet");
459     package.session().close();
460 }
461
462 void yf::Zoom::Frontend::handle_package(mp::Package &package)
463 {
464     Z_GDU *gdu = package.request().get();
465     if (!gdu)
466         ;
467     else if (gdu->which == Z_GDU_Z3950)
468     {
469         Z_APDU *apdu_req = gdu->u.z3950;
470         if (apdu_req->which == Z_APDU_initRequest)
471         {
472             mp::odr odr;
473             package.response() = odr.create_close(
474                 apdu_req,
475                 Z_Close_protocolError,
476                 "double init");
477         }
478         else if (apdu_req->which == Z_APDU_searchRequest)
479         {
480             handle_search(package);
481         }
482         else if (apdu_req->which == Z_APDU_presentRequest)
483         {
484             handle_present(package);
485         }
486         else
487         {
488             mp::odr odr;
489             package.response() = odr.create_close(
490                 apdu_req,
491                 Z_Close_protocolError,
492                 "zoom filter cannot handle this APDU");
493             package.session().close();
494         }
495     }
496     else
497     {
498         package.session().close();
499     }
500 }
501
502 void yf::Zoom::Impl::process(mp::Package &package)
503 {
504     FrontendPtr f = get_frontend(package);
505     Z_GDU *gdu = package.request().get();
506
507     if (f->m_is_virtual)
508     {
509         f->handle_package(package);
510     }
511     else if (gdu && gdu->which == Z_GDU_Z3950 && gdu->u.z3950->which ==
512              Z_APDU_initRequest)
513     {
514         Z_InitRequest *req = gdu->u.z3950->u.initRequest;
515         f->m_init_gdu = gdu;
516         
517         mp::odr odr;
518         Z_APDU *apdu = odr.create_initResponse(gdu->u.z3950, 0, 0);
519         Z_InitResponse *resp = apdu->u.initResponse;
520         
521         int i;
522         static const int masks[] = {
523             Z_Options_search,
524             Z_Options_present,
525             -1 
526         };
527         for (i = 0; masks[i] != -1; i++)
528             if (ODR_MASK_GET(req->options, masks[i]))
529                 ODR_MASK_SET(resp->options, masks[i]);
530         
531         static const int versions[] = {
532             Z_ProtocolVersion_1,
533             Z_ProtocolVersion_2,
534             Z_ProtocolVersion_3,
535             -1
536         };
537         for (i = 0; versions[i] != -1; i++)
538             if (ODR_MASK_GET(req->protocolVersion, versions[i]))
539                 ODR_MASK_SET(resp->protocolVersion, versions[i]);
540             else
541                 break;
542         
543         *resp->preferredMessageSize = *req->preferredMessageSize;
544         *resp->maximumRecordSize = *req->maximumRecordSize;
545         
546         package.response() = apdu;
547         f->m_is_virtual = true;
548     }
549     else
550         package.move();
551
552     release_frontend(package);
553 }
554
555
556 static mp::filter::Base* filter_creator()
557 {
558     return new mp::filter::Zoom;
559 }
560
561 extern "C" {
562     struct metaproxy_1_filter_struct metaproxy_1_filter_zoom = {
563         0,
564         "zoom",
565         filter_creator
566     };
567 }
568
569
570 /*
571  * Local variables:
572  * c-basic-offset: 4
573  * c-file-style: "Stroustrup"
574  * indent-tabs-mode: nil
575  * End:
576  * vim: shiftwidth=4 tabstop=8 expandtab
577  */
578