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