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