1 /* $Id: filter_virt_db.cpp,v 1.29 2006-01-17 13:34:51 adam Exp $
2 Copyright (c) 2005, Index Data.
10 #include "package.hpp"
12 #include <boost/thread/mutex.hpp>
13 #include <boost/thread/condition.hpp>
14 #include <boost/shared_ptr.hpp>
17 #include "filter_virt_db.hpp"
20 #include <yaz/otherinfo.h>
21 #include <yaz/diagbib1.h>
26 namespace yf = yp2::filter;
32 Set(BackendPtr b, std::string setname);
37 std::string m_setname;
40 Map(std::list<std::string> targets, std::string route);
42 std::list<std::string> m_targets;
45 struct Virt_db::Backend {
46 yp2::Session m_backend_session;
47 std::list<std::string> m_frontend_databases;
48 std::list<std::string> m_targets;
50 bool m_named_result_sets;
53 struct Virt_db::Frontend {
56 yp2::Session m_session;
59 std::list<BackendPtr> m_backend_list;
60 std::map<std::string,Virt_db::Set> m_sets;
62 void search(Package &package, Z_APDU *apdu);
63 void present(Package &package, Z_APDU *apdu);
64 void scan(Package &package, Z_APDU *apdu);
66 void close(Package &package);
67 typedef std::map<std::string,Virt_db::Set>::iterator Sets_it;
69 BackendPtr lookup_backend_from_databases(
70 std::list<std::string> databases);
71 BackendPtr create_backend_from_databases(
72 std::list<std::string> databases,
73 std::string &failing_database);
75 BackendPtr init_backend(std::list<std::string> database,
77 int &error_code, std::string &addinfo);
82 friend class Frontend;
84 FrontendPtr get_frontend(Package &package);
85 void release_frontend(Package &package);
87 boost::mutex m_sessions_mutex;
88 std::map<std::string, Virt_db::Map>m_maps;
90 typedef std::map<std::string,Virt_db::Set>::iterator Sets_it;
93 boost::condition m_cond_session_ready;
94 std::map<yp2::Session, FrontendPtr> m_clients;
101 yf::Virt_db::BackendPtr yf::Virt_db::Frontend::lookup_backend_from_databases(
102 std::list<std::string> databases)
104 std::list<BackendPtr>::const_iterator map_it;
105 map_it = m_backend_list.begin();
106 for (; map_it != m_backend_list.end(); map_it++)
107 if ((*map_it)->m_frontend_databases == databases)
113 yf::Virt_db::BackendPtr yf::Virt_db::Frontend::create_backend_from_databases(
114 std::list<std::string> databases, std::string &failing_database)
116 BackendPtr b(new Backend);
117 std::list<std::string>::const_iterator db_it = databases.begin();
119 b->m_number_of_sets = 0;
120 b->m_frontend_databases = databases;
121 b->m_named_result_sets = false;
123 std::map<std::string,bool> targets_dedup;
124 for (; db_it != databases.end(); db_it++)
126 std::map<std::string, Virt_db::Map>::iterator map_it;
127 map_it = m_p->m_maps.find(*db_it);
128 if (map_it == m_p->m_maps.end()) // database not found
130 failing_database = *db_it;
134 std::list<std::string>::const_iterator t_it =
135 map_it->second.m_targets.begin();
136 for (; t_it != map_it->second.m_targets.end(); t_it++)
137 targets_dedup[*t_it] = true;
138 b->m_route = map_it->second.m_route;
140 std::map<std::string,bool>::const_iterator tm_it = targets_dedup.begin();
141 for (; tm_it != targets_dedup.end(); tm_it++)
142 b->m_targets.push_back(tm_it->first);
147 yf::Virt_db::BackendPtr yf::Virt_db::Frontend::init_backend(
148 std::list<std::string> databases, Package &package,
149 int &error_code, std::string &addinfo)
151 std::string failing_database;
152 BackendPtr b = create_backend_from_databases(databases, failing_database);
155 error_code = YAZ_BIB1_DATABASE_UNAVAILABLE;
156 addinfo = failing_database;
159 Package init_package(b->m_backend_session, package.origin());
160 init_package.copy_filter(package);
164 Z_APDU *init_apdu = zget_APDU(odr, Z_APDU_initRequest);
166 std::list<std::string>::const_iterator t_it = b->m_targets.begin();
168 for (; t_it != b->m_targets.end(); t_it++, cat++)
170 yaz_oi_set_string_oidval(&init_apdu->u.initRequest->otherInfo, odr,
171 VAL_PROXY, cat, t_it->c_str());
174 Z_InitRequest *req = init_apdu->u.initRequest;
176 ODR_MASK_SET(req->options, Z_Options_search);
177 ODR_MASK_SET(req->options, Z_Options_present);
178 ODR_MASK_SET(req->options, Z_Options_namedResultSets);
179 ODR_MASK_SET(req->options, Z_Options_scan);
181 ODR_MASK_SET(req->protocolVersion, Z_ProtocolVersion_1);
182 ODR_MASK_SET(req->protocolVersion, Z_ProtocolVersion_2);
183 ODR_MASK_SET(req->protocolVersion, Z_ProtocolVersion_3);
185 init_package.request() = init_apdu;
187 init_package.move(b->m_route); // sending init
189 if (init_package.session().is_closed())
191 error_code = YAZ_BIB1_DATABASE_UNAVAILABLE;
192 // addinfo = database;
196 Z_GDU *gdu = init_package.response().get();
197 // we hope to get an init response
198 if (gdu && gdu->which == Z_GDU_Z3950 && gdu->u.z3950->which ==
201 if (ODR_MASK_GET(gdu->u.z3950->u.initResponse->options,
202 Z_Options_namedResultSets))
204 b->m_named_result_sets = true;
209 error_code = YAZ_BIB1_DATABASE_UNAVAILABLE;
210 // addinfo = database;
214 m_backend_list.push_back(b);
218 void yf::Virt_db::Frontend::search(Package &package, Z_APDU *apdu_req)
220 Z_SearchRequest *req = apdu_req->u.searchRequest;
222 std::string resultSetId = req->resultSetName;
225 std::list<std::string> databases;
227 for (i = 0; i<req->num_databaseNames; i++)
228 databases.push_back(req->databaseNames[i]);
230 BackendPtr b; // null for now
231 Sets_it sets_it = m_sets.find(req->resultSetName);
232 if (sets_it != m_sets.end())
234 // result set already exist
235 // if replace indicator is off: we return diagnostic if
236 // result set already exist.
237 if (*req->replaceIndicator == 0)
240 odr.create_searchResponse(
242 YAZ_BIB1_RESULT_SET_EXISTS_AND_REPLACE_INDICATOR_OFF,
244 package.response() = apdu;
248 sets_it->second.m_backend->m_number_of_sets--;
250 // pick up any existing backend with a database match
251 std::list<BackendPtr>::const_iterator map_it;
252 map_it = m_backend_list.begin();
253 for (; map_it != m_backend_list.end(); map_it++)
255 BackendPtr tmp = *map_it;
256 if (tmp->m_frontend_databases == databases)
259 if (map_it != m_backend_list.end())
266 // pick up any existing database with named result sets ..
267 // or one which has no result sets.. yet.
268 std::list<BackendPtr>::const_iterator map_it;
269 map_it = m_backend_list.begin();
270 for (; map_it != m_backend_list.end(); map_it++)
272 BackendPtr tmp = *map_it;
273 if (tmp->m_frontend_databases == databases &&
274 (tmp->m_named_result_sets ||
275 tmp->m_number_of_sets == 0))
278 if (map_it != m_backend_list.end())
281 if (!b) // no backend yet. Must create a new one
285 b = init_backend(databases, package, error_code, addinfo);
288 // did not get a backend (unavailable somehow?)
291 odr.create_searchResponse(
292 apdu_req, error_code, addinfo.c_str());
293 package.response() = apdu;
297 m_sets.erase(req->resultSetName);
298 // sending search to backend
299 Package search_package(b->m_backend_session, package.origin());
301 search_package.copy_filter(package);
303 std::string backend_setname;
304 if (b->m_named_result_sets)
306 backend_setname = std::string(req->resultSetName);
310 backend_setname = "default";
311 req->resultSetName = odr_strdup(odr, backend_setname.c_str());
314 // pick first targets spec and move the databases from it ..
315 std::list<std::string>::const_iterator t_it = b->m_targets.begin();
316 if (t_it != b->m_targets.end())
318 if (!yp2::util::set_databases_from_zurl(odr, *t_it,
319 &req->num_databaseNames,
320 &req->databaseNames));
323 *req->replaceIndicator = 1;
325 search_package.request() = yazpp_1::GDU(apdu_req);
327 search_package.move(b->m_route);
329 if (search_package.session().is_closed())
331 package.response() = search_package.response();
332 package.session().close();
335 package.response() = search_package.response();
337 b->m_number_of_sets++;
339 m_sets[resultSetId] = Virt_db::Set(b, backend_setname);
342 yf::Virt_db::Frontend::Frontend(Rep *rep)
345 m_is_virtual = false;
348 void yf::Virt_db::Frontend::close(Package &package)
350 std::list<BackendPtr>::const_iterator b_it;
352 for (b_it = m_backend_list.begin(); b_it != m_backend_list.end(); b_it++)
354 (*b_it)->m_backend_session.close();
355 Package close_package((*b_it)->m_backend_session, package.origin());
356 close_package.copy_filter(package);
357 close_package.move((*b_it)->m_route);
359 m_backend_list.clear();
362 yf::Virt_db::Frontend::~Frontend()
366 yf::Virt_db::FrontendPtr yf::Virt_db::Rep::get_frontend(Package &package)
368 boost::mutex::scoped_lock lock(m_mutex);
370 std::map<yp2::Session,yf::Virt_db::FrontendPtr>::iterator it;
374 it = m_clients.find(package.session());
375 if (it == m_clients.end())
378 if (!it->second->m_in_use)
380 it->second->m_in_use = true;
383 m_cond_session_ready.wait(lock);
385 FrontendPtr f(new Frontend(this));
386 m_clients[package.session()] = f;
391 void yf::Virt_db::Rep::release_frontend(Package &package)
393 boost::mutex::scoped_lock lock(m_mutex);
394 std::map<yp2::Session,yf::Virt_db::FrontendPtr>::iterator it;
396 it = m_clients.find(package.session());
397 if (it != m_clients.end())
399 if (package.session().is_closed())
401 it->second->close(package);
406 it->second->m_in_use = false;
408 m_cond_session_ready.notify_all();
412 yf::Virt_db::Set::Set(BackendPtr b, std::string setname)
413 : m_backend(b), m_setname(setname)
418 yf::Virt_db::Set::Set()
423 yf::Virt_db::Set::~Set()
427 yf::Virt_db::Map::Map(std::list<std::string> targets, std::string route)
428 : m_targets(targets), m_route(route)
432 yf::Virt_db::Map::Map()
436 yf::Virt_db::Virt_db() : m_p(new Virt_db::Rep)
440 yf::Virt_db::~Virt_db() {
443 void yf::Virt_db::Frontend::present(Package &package, Z_APDU *apdu_req)
445 Z_PresentRequest *req = apdu_req->u.presentRequest;
446 std::string resultSetId = req->resultSetId;
449 Sets_it sets_it = m_sets.find(resultSetId);
450 if (sets_it == m_sets.end())
453 odr.create_presentResponse(
455 YAZ_BIB1_SPECIFIED_RESULT_SET_DOES_NOT_EXIST,
456 resultSetId.c_str());
457 package.response() = apdu;
461 new yp2::Session(sets_it->second.m_backend->m_backend_session);
463 // sending present to backend
464 Package present_package(*id, package.origin());
465 present_package.copy_filter(package);
467 req->resultSetId = odr_strdup(odr, sets_it->second.m_setname.c_str());
469 present_package.request() = yazpp_1::GDU(apdu_req);
471 present_package.move();
473 if (present_package.session().is_closed())
475 package.response() = present_package.response();
476 package.session().close();
481 package.response() = present_package.response();
486 void yf::Virt_db::Frontend::scan(Package &package, Z_APDU *apdu_req)
488 Z_ScanRequest *req = apdu_req->u.scanRequest;
492 std::list<std::string> databases;
494 for (i = 0; i<req->num_databaseNames; i++)
495 databases.push_back(req->databaseNames[i]);
498 // pick up any existing backend with a database match
499 std::list<BackendPtr>::const_iterator map_it;
500 map_it = m_backend_list.begin();
501 for (; map_it != m_backend_list.end(); map_it++)
503 BackendPtr tmp = *map_it;
504 if (tmp->m_frontend_databases == databases)
507 if (map_it != m_backend_list.end())
509 if (!b) // no backend yet. Must create a new one
513 b = init_backend(databases, package, error_code, addinfo);
516 // did not get a backend (unavailable somehow?)
518 odr.create_scanResponse(
519 apdu_req, error_code, addinfo.c_str());
520 package.response() = apdu;
525 // sending scan to backend
526 Package scan_package(b->m_backend_session, package.origin());
528 scan_package.copy_filter(package);
530 // pick first targets spec and move the databases from it ..
531 std::list<std::string>::const_iterator t_it = b->m_targets.begin();
532 if (t_it != b->m_targets.end())
534 if (!yp2::util::set_databases_from_zurl(odr, *t_it,
535 &req->num_databaseNames,
536 &req->databaseNames));
538 scan_package.request() = yazpp_1::GDU(apdu_req);
540 scan_package.move(b->m_route);
542 if (scan_package.session().is_closed())
544 package.response() = scan_package.response();
545 package.session().close();
548 package.response() = scan_package.response();
552 void yf::Virt_db::add_map_db2targets(std::string db,
553 std::list<std::string> targets,
556 m_p->m_maps[db] = Virt_db::Map(targets, route);
560 void yf::Virt_db::add_map_db2target(std::string db,
564 std::list<std::string> targets;
565 targets.push_back(target);
567 m_p->m_maps[db] = Virt_db::Map(targets, route);
570 void yf::Virt_db::process(Package &package) const
572 FrontendPtr f = m_p->get_frontend(package);
574 Z_GDU *gdu = package.request().get();
576 if (gdu && gdu->which == Z_GDU_Z3950 && gdu->u.z3950->which ==
577 Z_APDU_initRequest && !f->m_is_virtual)
579 Z_InitRequest *req = gdu->u.z3950->u.initRequest;
582 yaz_oi_get_string_oidval(&req->otherInfo, VAL_PROXY, 1, 0);
586 Z_APDU *apdu = odr.create_initResponse(gdu->u.z3950, 0, 0);
587 Z_InitResponse *resp = apdu->u.initResponse;
590 static const int masks[] = {
593 Z_Options_namedResultSets,
597 for (i = 0; masks[i] != -1; i++)
598 if (ODR_MASK_GET(req->options, masks[i]))
599 ODR_MASK_SET(resp->options, masks[i]);
601 static const int versions[] = {
607 for (i = 0; versions[i] != -1; i++)
608 if (ODR_MASK_GET(req->protocolVersion, versions[i]))
609 ODR_MASK_SET(resp->protocolVersion, versions[i]);
613 package.response() = apdu;
614 f->m_is_virtual = true;
619 else if (!f->m_is_virtual)
621 else if (gdu && gdu->which == Z_GDU_Z3950)
623 Z_APDU *apdu = gdu->u.z3950;
624 if (apdu->which == Z_APDU_initRequest)
628 package.response() = odr.create_close(
630 Z_Close_protocolError,
633 package.session().close();
635 else if (apdu->which == Z_APDU_searchRequest)
637 f->search(package, apdu);
639 else if (apdu->which == Z_APDU_presentRequest)
641 f->present(package, apdu);
643 else if (apdu->which == Z_APDU_scanRequest)
645 f->scan(package, apdu);
651 package.response() = odr.create_close(
652 apdu, Z_Close_protocolError,
653 "unsupported APDU in filter_virt_db");
655 package.session().close();
658 m_p->release_frontend(package);
662 void yp2::filter::Virt_db::configure(const xmlNode * ptr)
664 for (ptr = ptr->children; ptr; ptr = ptr->next)
666 if (ptr->type != XML_ELEMENT_NODE)
668 if (!strcmp((const char *) ptr->name, "virtual"))
670 std::string database;
671 std::list<std::string> targets;
672 xmlNode *v_node = ptr->children;
673 for (; v_node; v_node = v_node->next)
675 if (v_node->type != XML_ELEMENT_NODE)
678 if (yp2::xml::is_element_yp2(v_node, "database"))
679 database = yp2::xml::get_text(v_node);
680 else if (yp2::xml::is_element_yp2(v_node, "target"))
681 targets.push_back(yp2::xml::get_text(v_node));
683 throw yp2::filter::FilterException
685 + std::string((const char *) v_node->name)
686 + " in virtual section"
689 std::string route = yp2::xml::get_route(ptr);
690 add_map_db2targets(database, targets, route);
694 throw yp2::filter::FilterException
696 + std::string((const char *) ptr->name)
697 + " in virt_db filter");
702 static yp2::filter::Base* filter_creator()
704 return new yp2::filter::Virt_db;
708 struct yp2_filter_struct yp2_filter_virt_db = {
719 * indent-tabs-mode: nil
720 * c-file-style: "stroustrup"
722 * vim: shiftwidth=4 tabstop=8 expandtab