Router: use shared_ptr rather than naked one
[metaproxy-moved-to-github.git] / src / filter_virt_db.cpp
1 /* This file is part of Metaproxy.
2    Copyright (C) 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 "filter_virt_db.hpp"
22 #include <metaproxy/package.hpp>
23
24 #include <boost/thread/mutex.hpp>
25 #include <boost/thread/condition.hpp>
26 #include <boost/shared_ptr.hpp>
27
28 #include <metaproxy/util.hpp>
29
30 #include <yaz/zgdu.h>
31 #include <yaz/otherinfo.h>
32 #include <yaz/diagbib1.h>
33 #include <yaz/match_glob.h>
34 #include <yaz/log.h>
35
36 #include <map>
37 #include <iostream>
38
39 namespace mp = metaproxy_1;
40 namespace yf = mp::filter;
41
42 namespace metaproxy_1 {
43     namespace filter {
44
45         struct VirtualDB::Set {
46             Set(BackendPtr b, std::string setname);
47             Set();
48             ~Set();
49
50             BackendPtr m_backend;
51             std::string m_setname;
52         };
53         struct VirtualDB::Map {
54             Map(std::string database, std::list<std::string> targets, std::string route);
55             Map(std::string database, std::string target, std::string route);
56             Map();
57             bool match(const std::string db) const;
58             std::string m_dbpattern;
59             std::list<std::string> m_targets;
60             std::string m_route;
61         };
62         struct VirtualDB::Backend {
63             mp::Session m_backend_session;
64             std::list<std::string> m_frontend_databases;
65             std::list<std::string> m_targets;
66             std::string m_route;
67             bool m_named_result_sets;
68             int m_number_of_sets;
69         };
70         struct VirtualDB::Frontend {
71             Frontend(Rep *rep);
72             ~Frontend();
73             mp::Session m_session;
74             bool m_is_virtual;
75             bool m_in_use;
76             yazpp_1::GDU m_init_gdu;
77             std::list<BackendPtr> m_backend_list;
78             std::map<std::string,VirtualDB::Set> m_sets;
79
80             void search(Package &package, Z_APDU *apdu);
81             void present(Package &package, Z_APDU *apdu);
82             void scan(Package &package, Z_APDU *apdu);
83             int relay_apdu(Package &package, Z_APDU *apdu);
84
85             void close(Package &package);
86             typedef std::map<std::string,VirtualDB::Set>::iterator Sets_it;
87
88             void fixup_package(Package &p, BackendPtr b);
89             void fixup_npr_record(ODR odr, Z_NamePlusRecord *npr,
90                                   BackendPtr b);
91             void fixup_npr_records(ODR odr, Z_Records *records,
92                                    BackendPtr b);
93
94             BackendPtr lookup_backend_from_databases(
95                 std::list<std::string> databases);
96             BackendPtr create_backend_from_databases(
97                 std::list<std::string> databases,
98                 int &error_code,
99                 std::string &failing_database);
100
101             BackendPtr init_backend(std::list<std::string> database,
102                                     Package &package,
103                                     int &error_code, std::string &addinfo);
104             Rep *m_p;
105         };
106         class VirtualDB::Rep {
107             friend class VirtualDB;
108             friend struct Frontend;
109
110             FrontendPtr get_frontend(Package &package);
111             void release_frontend(Package &package);
112             void refresh_torus();
113         private:
114             std::list<VirtualDB::Map>m_maps;
115             typedef std::map<std::string,VirtualDB::Set>::iterator Sets_it;
116             boost::mutex m_mutex;
117             boost::condition m_cond_session_ready;
118             std::map<mp::Session, FrontendPtr> m_clients;
119             bool pass_vhosts;
120         };
121     }
122 }
123
124 yf::VirtualDB::BackendPtr yf::VirtualDB::Frontend::lookup_backend_from_databases(
125     std::list<std::string> databases)
126 {
127     std::list<BackendPtr>::const_iterator map_it;
128     map_it = m_backend_list.begin();
129     for (; map_it != m_backend_list.end(); map_it++)
130         if ((*map_it)->m_frontend_databases == databases)
131             return *map_it;
132     BackendPtr null;
133     return null;
134 }
135
136 yf::VirtualDB::BackendPtr yf::VirtualDB::Frontend::create_backend_from_databases(
137     std::list<std::string> databases, int &error_code, std::string &addinfo)
138 {
139     BackendPtr b(new Backend);
140     std::list<std::string>::const_iterator db_it = databases.begin();
141
142     b->m_number_of_sets = 0;
143     b->m_frontend_databases = databases;
144     b->m_named_result_sets = false;
145
146     bool first_route = true;
147
148     std::map<std::string,bool> targets_dedup;
149     for (; db_it != databases.end(); db_it++)
150     {
151         std::list<VirtualDB::Map>::const_iterator map_it;
152         map_it = m_p->m_maps.begin();
153         while (map_it != m_p->m_maps.end())
154         {
155             if (map_it->match(*db_it))
156                 break;
157             map_it++;
158         }
159
160         if (map_it == m_p->m_maps.end())  // database not found
161         {
162             error_code = YAZ_BIB1_DATABASE_DOES_NOT_EXIST;
163             addinfo = *db_it;
164             BackendPtr ptr;
165             return ptr;
166         }
167         std::list<std::string>::const_iterator t_it =
168             map_it->m_targets.begin();
169         for (; t_it != map_it->m_targets.end(); t_it++) {
170             if (!targets_dedup[*t_it])
171             {
172                 targets_dedup[*t_it] = true;
173                 b->m_targets.push_back(*t_it);
174             }
175         }
176
177         // see if we have a route conflict.
178         if (!first_route && b->m_route != map_it->m_route)
179         {
180             // we have a conflict.. routing must be same for all
181             error_code =  YAZ_BIB1_COMBI_OF_SPECIFIED_DATABASES_UNSUPP;
182             BackendPtr ptr;
183             return ptr;
184         }
185         b->m_route = map_it->m_route;
186         first_route = false;
187     }
188     return b;
189 }
190
191 yf::VirtualDB::BackendPtr yf::VirtualDB::Frontend::init_backend(
192     std::list<std::string> databases, mp::Package &package,
193     int &error_code, std::string &addinfo)
194 {
195     BackendPtr b = create_backend_from_databases(databases, error_code,
196                                                  addinfo);
197     if (!b)
198         return b;
199     Package init_package(b->m_backend_session, package.origin());
200     init_package.copy_filter(package);
201
202     mp::odr odr;
203
204     Z_APDU *init_apdu = zget_APDU(odr, Z_APDU_initRequest);
205
206     mp::util::set_vhost_otherinfo(&init_apdu->u.initRequest->otherInfo, odr,
207                                    b->m_targets);
208     Z_InitRequest *req = init_apdu->u.initRequest;
209
210     // copy stuff from Frontend Init Request
211     Z_GDU *org_gdu = m_init_gdu.get();
212     Z_InitRequest *org_init = org_gdu->u.z3950->u.initRequest;
213
214     req->idAuthentication = org_init->idAuthentication;
215     req->implementationId = org_init->implementationId;
216     req->implementationName = org_init->implementationName;
217     req->implementationVersion = org_init->implementationVersion;
218     *req->preferredMessageSize = *org_init->preferredMessageSize;
219     *req->maximumRecordSize = *org_init->maximumRecordSize;
220
221     ODR_MASK_SET(req->options, Z_Options_search);
222     ODR_MASK_SET(req->options, Z_Options_present);
223     ODR_MASK_SET(req->options, Z_Options_namedResultSets);
224     ODR_MASK_SET(req->options, Z_Options_scan);
225
226     ODR_MASK_SET(req->protocolVersion, Z_ProtocolVersion_1);
227     ODR_MASK_SET(req->protocolVersion, Z_ProtocolVersion_2);
228     ODR_MASK_SET(req->protocolVersion, Z_ProtocolVersion_3);
229
230     init_package.request() = init_apdu;
231
232     init_package.move(b->m_route);  // sending init
233
234     Z_GDU *gdu = init_package.response().get();
235     // we hope to get an init response
236     error_code = 0;
237     if (gdu && gdu->which == Z_GDU_Z3950
238         && gdu->u.z3950->which == Z_APDU_initResponse)
239     {
240         Z_InitResponse *res = gdu->u.z3950->u.initResponse;
241         if (ODR_MASK_GET(res->options, Z_Options_namedResultSets))
242         {
243             b->m_named_result_sets = true;
244         }
245         if (*res->result && !init_package.session().is_closed())
246         {
247             m_backend_list.push_back(b);
248             return b;
249         }
250         mp::util::get_init_diagnostics(res, error_code, addinfo);
251     }
252     if (error_code == 0)
253     {
254         std::list<std::string>::const_iterator db_it = databases.begin();
255         error_code = YAZ_BIB1_ACCESS_TO_SPECIFIED_DATABASE_DENIED;
256         if (db_it != databases.end())
257             addinfo = *db_it;
258     }
259     if (!init_package.session().is_closed())
260     {
261         Package close_package(b->m_backend_session, package.origin());
262         close_package.copy_filter(package);
263         close_package.session().close();
264         close_package.move(b->m_route);  // closing it
265     }
266     BackendPtr null;
267     return null;
268 }
269
270 void yf::VirtualDB::Frontend::search(mp::Package &package, Z_APDU *apdu_req)
271 {
272     yazpp_1::GDU ngdu(apdu_req);
273     Z_SearchRequest *req = ngdu.get()->u.z3950->u.searchRequest;
274     std::string vhost;
275     std::string resultSetId = req->resultSetName;
276     mp::odr odr;
277
278     std::list<std::string> databases;
279     int i;
280     for (i = 0; i<req->num_databaseNames; i++)
281         databases.push_back(req->databaseNames[i]);
282
283     Sets_it sets_it = m_sets.find(req->resultSetName);
284     if (sets_it != m_sets.end())
285     {
286         // result set already exist
287         // if replace indicator is off: we return diagnostic if
288         // result set already exist.
289         if (*req->replaceIndicator == 0)
290         {
291             Z_APDU *apdu =
292                 odr.create_searchResponse(
293                     apdu_req,
294                     YAZ_BIB1_RESULT_SET_EXISTS_AND_REPLACE_INDICATOR_OFF,
295                     0);
296             package.response() = apdu;
297
298             return;
299         }
300         sets_it->second.m_backend->m_number_of_sets--;
301     }
302     // pick up any existing database with named result sets ..
303     // or one which has no result sets.. yet.
304     BackendPtr b; // null for now
305     std::list<BackendPtr>::const_iterator map_it;
306     map_it = m_backend_list.begin();
307     for (; map_it != m_backend_list.end(); map_it++)
308     {
309         BackendPtr tmp = *map_it;
310         if (tmp->m_frontend_databases == databases &&
311             (tmp->m_named_result_sets ||
312              tmp->m_number_of_sets == 0))
313         {
314             b = *map_it;
315             break;
316         }
317     }
318     if (!b)  // no backend yet. Must create a new one
319     {
320         int error_code;
321         std::string addinfo;
322         b = init_backend(databases, package, error_code, addinfo);
323         if (!b)
324         {
325             // did not get a backend (unavailable somehow?)
326
327             Z_APDU *apdu =
328                 odr.create_searchResponse(
329                     apdu_req, error_code, addinfo.c_str());
330             package.response() = apdu;
331             return;
332         }
333     }
334     m_sets.erase(req->resultSetName);
335     // sending search to backend
336     Package search_package(b->m_backend_session, package.origin());
337
338     search_package.copy_filter(package);
339
340     std::string backend_setname;
341     if (b->m_named_result_sets)
342     {
343         backend_setname = std::string(req->resultSetName);
344     }
345     else
346     {
347         backend_setname = "default";
348         req->resultSetName = odr_strdup(odr, backend_setname.c_str());
349     }
350
351     // pick first targets spec and move the databases from it ..
352     std::list<std::string>::const_iterator t_it = b->m_targets.begin();
353     if (t_it != b->m_targets.end())
354     {
355         mp::util::set_databases_from_zurl(odr, *t_it,
356                                                 &req->num_databaseNames,
357                                                 &req->databaseNames);
358     }
359
360     *req->replaceIndicator = 1;
361
362     search_package.request() = ngdu;
363
364     search_package.move(b->m_route);
365
366     if (search_package.session().is_closed())
367     {
368         package.response() = search_package.response();
369         package.session().close();
370         return;
371     }
372     b->m_number_of_sets++;
373
374     m_sets[resultSetId] = VirtualDB::Set(b, backend_setname);
375     fixup_package(search_package, b);
376     package.response() = search_package.response();
377 }
378
379 yf::VirtualDB::Frontend::Frontend(Rep *rep)
380 {
381     m_p = rep;
382     m_is_virtual = false;
383 }
384
385 void yf::VirtualDB::Frontend::close(mp::Package &package)
386 {
387     std::list<BackendPtr>::const_iterator b_it;
388
389     for (b_it = m_backend_list.begin(); b_it != m_backend_list.end(); b_it++)
390     {
391         (*b_it)->m_backend_session.close();
392         Package close_package((*b_it)->m_backend_session, package.origin());
393         close_package.copy_filter(package);
394         close_package.move((*b_it)->m_route);
395     }
396     m_backend_list.clear();
397 }
398
399 yf::VirtualDB::Frontend::~Frontend()
400 {
401 }
402
403 yf::VirtualDB::FrontendPtr yf::VirtualDB::Rep::get_frontend(mp::Package &package)
404 {
405     boost::mutex::scoped_lock lock(m_mutex);
406
407     std::map<mp::Session,yf::VirtualDB::FrontendPtr>::iterator it;
408
409     while(true)
410     {
411         it = m_clients.find(package.session());
412         if (it == m_clients.end())
413             break;
414
415         if (!it->second->m_in_use)
416         {
417             it->second->m_in_use = true;
418             return it->second;
419         }
420         m_cond_session_ready.wait(lock);
421     }
422     FrontendPtr f(new Frontend(this));
423     m_clients[package.session()] = f;
424     f->m_in_use = true;
425     return f;
426 }
427
428 void yf::VirtualDB::Rep::release_frontend(mp::Package &package)
429 {
430     boost::mutex::scoped_lock lock(m_mutex);
431     std::map<mp::Session,yf::VirtualDB::FrontendPtr>::iterator it;
432
433     it = m_clients.find(package.session());
434     if (it != m_clients.end())
435     {
436         if (package.session().is_closed())
437         {
438             it->second->close(package);
439             m_clients.erase(it);
440         }
441         else
442         {
443             it->second->m_in_use = false;
444         }
445         m_cond_session_ready.notify_all();
446     }
447 }
448
449
450 yf::VirtualDB::Set::Set(BackendPtr b, std::string setname)
451     :  m_backend(b), m_setname(setname)
452 {
453 }
454
455
456 yf::VirtualDB::Set::Set()
457 {
458 }
459
460
461 yf::VirtualDB::Set::~Set()
462 {
463 }
464
465 yf::VirtualDB::Map::Map(std::string database,
466                         std::list<std::string> targets, std::string route)
467     : m_dbpattern(database), m_targets(targets), m_route(route)
468 {
469 }
470
471 yf::VirtualDB::Map::Map(std::string database,
472                         std::string target, std::string route)
473     : m_dbpattern(database), m_route(route)
474 {
475     m_targets.push_back(target);
476 }
477
478
479 yf::VirtualDB::Map::Map()
480 {
481 }
482
483 bool yf::VirtualDB::Map::match(const std::string db) const
484 {
485     std::string norm_db = mp::util::database_name_normalize(db);
486     if (yaz_match_glob(m_dbpattern.c_str(), norm_db.c_str()))
487         return true;
488     return false;
489 }
490
491 yf::VirtualDB::VirtualDB() : m_p(new VirtualDB::Rep)
492 {
493     m_p->pass_vhosts = false;
494 }
495
496 yf::VirtualDB::~VirtualDB() {
497 }
498
499 void yf::VirtualDB::Frontend::fixup_npr_record(ODR odr, Z_NamePlusRecord *npr,
500                                              BackendPtr b)
501 {
502     if (npr->databaseName)
503     {
504         std::string b_database = std::string(npr->databaseName);
505
506         // consider each of the frontend databases..
507         std::list<std::string>::const_iterator db_it;
508         for (db_it = b->m_frontend_databases.begin();
509              db_it != b->m_frontend_databases.end(); db_it++)
510         {
511             // see which target it corresponds to.. (if any)
512             std::list<VirtualDB::Map>::const_iterator map_it =
513                 m_p->m_maps.begin();
514             while (map_it != m_p->m_maps.end())
515             {
516                 if (map_it->match(*db_it))
517                     break;
518                 map_it++;
519             }
520             if (map_it != m_p->m_maps.end())
521             {
522                 std::list<std::string>::const_iterator t
523                     = map_it->m_targets.begin();
524                 while (t != map_it->m_targets.end())
525                 {
526                     if (*t == b_database)
527                     {
528                         npr->databaseName = odr_strdup(odr, (*db_it).c_str());
529                         return;
530                     }
531                     t++;
532                 }
533             }
534
535         }
536         db_it = b->m_frontend_databases.begin();
537         if (db_it != b->m_frontend_databases.end())
538         {
539             std::string database = *db_it;
540             npr->databaseName = odr_strdup(odr, database.c_str());
541         }
542     }
543 }
544
545 void yf::VirtualDB::Frontend::fixup_npr_records(ODR odr, Z_Records *records,
546                                               BackendPtr b)
547 {
548     if (records && records->which == Z_Records_DBOSD)
549     {
550         Z_NamePlusRecordList *nprlist = records->u.databaseOrSurDiagnostics;
551         int i;
552         for (i = 0; i < nprlist->num_records; i++)
553         {
554             fixup_npr_record(odr, nprlist->records[i], b);
555         }
556     }
557 }
558
559 void yf::VirtualDB::Frontend::fixup_package(mp::Package &p, BackendPtr b)
560 {
561     Z_GDU *gdu = p.response().get();
562     mp::odr odr;
563
564     if (gdu && gdu->which == Z_GDU_Z3950)
565     {
566         Z_APDU *apdu = gdu->u.z3950;
567         if (apdu->which == Z_APDU_presentResponse)
568         {
569             fixup_npr_records(odr, apdu->u.presentResponse->records, b);
570             p.response() = gdu;
571         }
572         else if (apdu->which == Z_APDU_searchResponse)
573         {
574             fixup_npr_records(odr,  apdu->u.searchResponse->records, b);
575             p.response() = gdu;
576         }
577     }
578 }
579
580 void yf::VirtualDB::Frontend::present(mp::Package &package, Z_APDU *apdu_req)
581 {
582     yazpp_1::GDU ngdu(apdu_req);
583     Z_PresentRequest *req = ngdu.get()->u.z3950->u.presentRequest;
584     std::string resultSetId = req->resultSetId;
585     mp::odr odr;
586
587     Sets_it sets_it = m_sets.find(resultSetId);
588     if (sets_it == m_sets.end())
589     {
590         Z_APDU *apdu =
591             odr.create_presentResponse(
592                 apdu_req,
593                 YAZ_BIB1_SPECIFIED_RESULT_SET_DOES_NOT_EXIST,
594                 resultSetId.c_str());
595         package.response() = apdu;
596         return;
597     }
598     Session *id =
599         new mp::Session(sets_it->second.m_backend->m_backend_session);
600
601     // sending present to backend
602     Package present_package(*id, package.origin());
603     present_package.copy_filter(package);
604
605     req->resultSetId = odr_strdup(odr, sets_it->second.m_setname.c_str());
606
607     present_package.request() = ngdu;
608
609     present_package.move(sets_it->second.m_backend->m_route);
610
611     fixup_package(present_package, sets_it->second.m_backend);
612
613     if (present_package.session().is_closed())
614     {
615         package.response() = present_package.response();
616         package.session().close();
617     }
618     else
619     {
620         package.response() = present_package.response();
621     }
622     delete id;
623 }
624
625 int yf::VirtualDB::Frontend::relay_apdu(mp::Package &package, Z_APDU *apdu_req)
626 {
627     int no = 0;
628     std::list<BackendPtr>::const_iterator map_it;
629     map_it = m_backend_list.begin();
630     for (; map_it != m_backend_list.end(); map_it++)
631     {
632         BackendPtr b = *map_it;
633
634         Package relay_package(b->m_backend_session, package.origin());
635         relay_package.copy_filter(package);
636
637         relay_package.request() = yazpp_1::GDU(apdu_req);
638
639         relay_package.move(b->m_route);
640         package.response() = relay_package.response();
641         no++;
642     }
643     return no;
644 }
645
646 void yf::VirtualDB::Frontend::scan(mp::Package &package, Z_APDU *apdu_req)
647 {
648     yazpp_1::GDU ngdu(apdu_req);
649     Z_ScanRequest *req = ngdu.get()->u.z3950->u.scanRequest;
650     std::string vhost;
651     mp::odr odr;
652
653     std::list<std::string> databases;
654     int i;
655     for (i = 0; i<req->num_databaseNames; i++)
656         databases.push_back(req->databaseNames[i]);
657
658     BackendPtr b;
659     // pick up any existing backend with a database match
660     std::list<BackendPtr>::const_iterator map_it;
661     map_it = m_backend_list.begin();
662     for (; map_it != m_backend_list.end(); map_it++)
663     {
664         BackendPtr tmp = *map_it;
665         if (tmp->m_frontend_databases == databases)
666             break;
667     }
668     if (map_it != m_backend_list.end())
669         b = *map_it;
670     if (!b)  // no backend yet. Must create a new one
671     {
672         int error_code;
673         std::string addinfo;
674         b = init_backend(databases, package, error_code, addinfo);
675         if (!b)
676         {
677             // did not get a backend (unavailable somehow?)
678             Z_APDU *apdu =
679                 odr.create_scanResponse(
680                     apdu_req, error_code, addinfo.c_str());
681             package.response() = apdu;
682
683             return;
684         }
685     }
686     // sending scan to backend
687     Package scan_package(b->m_backend_session, package.origin());
688
689     scan_package.copy_filter(package);
690
691     // pick first targets spec and move the databases from it ..
692     std::list<std::string>::const_iterator t_it = b->m_targets.begin();
693     if (t_it != b->m_targets.end())
694     {
695         mp::util::set_databases_from_zurl(odr, *t_it,
696                                                 &req->num_databaseNames,
697                                                 &req->databaseNames);
698     }
699
700     scan_package.request() = ngdu;
701
702     scan_package.move(b->m_route);
703
704     if (scan_package.session().is_closed())
705     {
706         package.response() = scan_package.response();
707         package.session().close();
708         return;
709     }
710     package.response() = scan_package.response();
711 }
712
713
714 void yf::VirtualDB::add_map_db2targets(std::string db,
715                                        std::list<std::string> targets,
716                                        std::string route)
717 {
718     m_p->m_maps.push_back(
719         VirtualDB::Map(mp::util::database_name_normalize(db), targets, route));
720 }
721
722
723 void yf::VirtualDB::add_map_db2target(std::string db,
724                                       std::string target,
725                                       std::string route)
726
727 {
728     m_p->m_maps.push_back(
729         VirtualDB::Map(mp::util::database_name_normalize(db), target, route));
730 }
731
732 void yf::VirtualDB::process(mp::Package &package) const
733 {
734     FrontendPtr f = m_p->get_frontend(package);
735
736     Z_GDU *gdu = package.request().get();
737
738     if (gdu && gdu->which == Z_GDU_Z3950 && gdu->u.z3950->which ==
739         Z_APDU_initRequest && !f->m_is_virtual)
740     {
741         Z_InitRequest *req = gdu->u.z3950->u.initRequest;
742
743         std::list<std::string> vhosts;
744         mp::util::get_vhost_otherinfo(req->otherInfo, vhosts);
745
746         if (vhosts.size() > 0 && m_p->pass_vhosts)
747         {
748             package.move();
749         }
750         else
751         {
752             f->m_init_gdu = gdu;
753
754             mp::odr odr;
755             Z_APDU *apdu = odr.create_initResponse(gdu->u.z3950, 0, 0);
756             Z_InitResponse *resp = apdu->u.initResponse;
757
758             int i;
759             static const int masks[] = {
760                 Z_Options_search,
761                 Z_Options_present,
762                 Z_Options_namedResultSets,
763                 Z_Options_scan,
764                 -1
765             };
766             for (i = 0; masks[i] != -1; i++)
767                 if (ODR_MASK_GET(req->options, masks[i]))
768                     ODR_MASK_SET(resp->options, masks[i]);
769
770             static const int versions[] = {
771                 Z_ProtocolVersion_1,
772                 Z_ProtocolVersion_2,
773                 Z_ProtocolVersion_3,
774                 -1
775             };
776             for (i = 0; versions[i] != -1; i++)
777                 if (ODR_MASK_GET(req->protocolVersion, versions[i]))
778                     ODR_MASK_SET(resp->protocolVersion, versions[i]);
779                 else
780                     break;
781
782             *resp->preferredMessageSize = *req->preferredMessageSize;
783             *resp->maximumRecordSize = *req->maximumRecordSize;
784
785             package.response() = apdu;
786             f->m_is_virtual = true;
787         }
788     }
789     else if (!f->m_is_virtual)
790         package.move();
791     else if (gdu && gdu->which == Z_GDU_Z3950)
792     {
793         Z_APDU *apdu = gdu->u.z3950;
794         if (apdu->which == Z_APDU_initRequest)
795         {
796             mp::odr odr;
797
798             package.response() = odr.create_close(
799                 apdu,
800                 Z_Close_protocolError,
801                 "double init");
802
803             package.session().close();
804         }
805         else if (apdu->which == Z_APDU_searchRequest)
806         {
807             f->search(package, apdu);
808         }
809         else if (apdu->which == Z_APDU_presentRequest)
810         {
811             f->present(package, apdu);
812         }
813         else if (apdu->which == Z_APDU_scanRequest)
814         {
815             f->scan(package, apdu);
816         }
817         else if (apdu->which == Z_APDU_close)
818         {
819             if (f->relay_apdu(package, apdu) == 0)
820             {
821                 mp::odr odr;
822
823                 package.response() = odr.create_close(
824                     apdu, Z_Close_finished, "virt_db");
825
826                 package.session().close();
827             }
828         }
829         else
830         {
831             mp::odr odr;
832
833             package.response() = odr.create_close(
834                 apdu, Z_Close_protocolError,
835                 "unsupported APDU in filter_virt_db");
836
837             package.session().close();
838         }
839     }
840     m_p->release_frontend(package);
841 }
842
843 void mp::filter::VirtualDB::configure(const xmlNode * ptr, bool test_only,
844                                       const char *path)
845 {
846     for (ptr = ptr->children; ptr; ptr = ptr->next)
847     {
848         if (ptr->type != XML_ELEMENT_NODE)
849             continue;
850         if (!strcmp((const char *) ptr->name, "pass-vhosts"))
851         {
852             m_p->pass_vhosts = mp::xml::get_bool(ptr, false);
853         }
854         else if (!strcmp((const char *) ptr->name, "virtual"))
855         {
856             std::string database;
857             std::list<std::string> targets;
858             xmlNode *v_node = ptr->children;
859             for (; v_node; v_node = v_node->next)
860             {
861                 if (v_node->type != XML_ELEMENT_NODE)
862                     continue;
863
864                 if (mp::xml::is_element_mp(v_node, "database"))
865                     database = mp::xml::get_text(v_node);
866                 else if (mp::xml::is_element_mp(v_node, "target"))
867                     targets.push_back(mp::xml::get_text(v_node));
868                 else
869                     throw mp::filter::FilterException
870                         ("Bad element "
871                          + std::string((const char *) v_node->name)
872                          + " in virtual section"
873                             );
874             }
875             std::string route = mp::xml::get_route(ptr);
876
877             VirtualDB::Map vmap(mp::util::database_name_normalize(database),
878                                 targets, route);
879             m_p->m_maps.push_back(vmap);
880         }
881         else
882         {
883             throw mp::filter::FilterException
884                 ("Bad element "
885                  + std::string((const char *) ptr->name)
886                  + " in virt_db filter");
887         }
888     }
889 }
890
891 static mp::filter::Base* filter_creator()
892 {
893     return new mp::filter::VirtualDB;
894 }
895
896 extern "C" {
897     struct metaproxy_1_filter_struct metaproxy_1_filter_virt_db = {
898         0,
899         "virt_db",
900         filter_creator
901     };
902 }
903
904
905 /*
906  * Local variables:
907  * c-basic-offset: 4
908  * c-file-style: "Stroustrup"
909  * indent-tabs-mode: nil
910  * End:
911  * vim: shiftwidth=4 tabstop=8 expandtab
912  */
913