1 /* This file is part of Metaproxy.
2 Copyright (C) 2005-2011 Index Data
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
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
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
20 #include "filter_zoom.hpp"
22 #include <metaproxy/package.hpp>
23 #include <metaproxy/util.hpp>
26 #include <boost/thread/mutex.hpp>
27 #include <boost/thread/condition.hpp>
28 #include <yaz/oid_db.h>
29 #include <yaz/diagbib1.h>
32 #include <yaz/querytowrbuf.h>
34 namespace mp = metaproxy_1;
35 namespace yf = mp::filter;
37 namespace metaproxy_1 {
39 struct Zoom::Searchable {
42 std::string query_encoding;
50 friend class Frontend;
52 ZOOM_connection m_connection;
53 ZOOM_resultset m_resultset;
54 std::string m_frontend_database;
58 void connect(std::string zurl, int *error, const char **addinfo);
59 void search_pqf(const char *pqf, Odr_int *hits,
60 int *error, const char **addinfo);
61 void present(Odr_int start, Odr_int number, ZOOM_record *recs,
62 int *error, const char **addinfo);
63 void set_option(const char *name, const char *value);
64 int get_error(const char **addinfo);
66 class Zoom::Frontend {
71 yazpp_1::GDU m_init_gdu;
73 void handle_package(mp::Package &package);
74 void handle_search(mp::Package &package);
75 void handle_present(mp::Package &package);
76 BackendPtr get_backend_from_databases(std::string &database,
78 const char **addinfo);
79 Z_Records *get_records(Odr_int start,
80 Odr_int number_to_present,
83 Odr_int *number_of_records_returned,
84 ODR odr, BackendPtr b,
85 Odr_oid *preferredRecordSyntax,
86 const char *element_set_name);
92 friend class Frontend;
96 void process(metaproxy_1::Package & package);
97 void configure(const xmlNode * ptr, bool test_only);
99 FrontendPtr get_frontend(mp::Package &package);
100 void release_frontend(mp::Package &package);
101 void parse_torus(const xmlNode *ptr);
103 std::list<Zoom::Searchable>m_searchables;
105 std::map<mp::Session, FrontendPtr> m_clients;
106 boost::mutex m_mutex;
107 boost::condition m_cond_session_ready;
113 // define Pimpl wrapper forwarding to Impl
115 yf::Zoom::Zoom() : m_p(new Impl)
120 { // must have a destructor because of boost::scoped_ptr
123 void yf::Zoom::configure(const xmlNode *xmlnode, bool test_only)
125 m_p->configure(xmlnode, test_only);
128 void yf::Zoom::process(mp::Package &package) const
130 m_p->process(package);
134 // define Implementation stuff
136 yf::Zoom::Backend::Backend()
138 m_connection = ZOOM_connection_create(0);
142 yf::Zoom::Backend::~Backend()
144 ZOOM_connection_destroy(m_connection);
145 ZOOM_resultset_destroy(m_resultset);
148 void yf::Zoom::Backend::connect(std::string zurl,
149 int *error, const char **addinfo)
151 ZOOM_connection_connect(m_connection, zurl.c_str(), 0);
152 *error = ZOOM_connection_error(m_connection, 0, addinfo);
155 void yf::Zoom::Backend::search_pqf(const char *pqf, Odr_int *hits,
156 int *error, const char **addinfo)
158 m_resultset = ZOOM_connection_search_pqf(m_connection, pqf);
159 *error = ZOOM_connection_error(m_connection, 0, addinfo);
161 *hits = ZOOM_resultset_size(m_resultset);
166 void yf::Zoom::Backend::present(Odr_int start, Odr_int number,
168 int *error, const char **addinfo)
170 ZOOM_resultset_records(m_resultset, recs, start, number);
171 *error = ZOOM_connection_error(m_connection, 0, addinfo);
174 void yf::Zoom::Backend::set_option(const char *name, const char *value)
176 ZOOM_connection_option_set(m_connection, name, value);
178 ZOOM_resultset_option_set(m_resultset, name, value);
181 int yf::Zoom::Backend::get_error(const char **addinfo)
183 return ZOOM_connection_error(m_connection, 0, addinfo);
186 yf::Zoom::Searchable::Searchable()
191 yf::Zoom::Searchable::~Searchable()
195 yf::Zoom::Frontend::Frontend(Impl *impl) :
196 m_p(impl), m_is_virtual(false), m_in_use(true)
200 yf::Zoom::Frontend::~Frontend()
204 yf::Zoom::FrontendPtr yf::Zoom::Impl::get_frontend(mp::Package &package)
206 boost::mutex::scoped_lock lock(m_mutex);
208 std::map<mp::Session,yf::Zoom::FrontendPtr>::iterator it;
212 it = m_clients.find(package.session());
213 if (it == m_clients.end())
216 if (!it->second->m_in_use)
218 it->second->m_in_use = true;
221 m_cond_session_ready.wait(lock);
223 FrontendPtr f(new Frontend(this));
224 m_clients[package.session()] = f;
229 void yf::Zoom::Impl::release_frontend(mp::Package &package)
231 boost::mutex::scoped_lock lock(m_mutex);
232 std::map<mp::Session,yf::Zoom::FrontendPtr>::iterator it;
234 it = m_clients.find(package.session());
235 if (it != m_clients.end())
237 if (package.session().is_closed())
243 it->second->m_in_use = false;
245 m_cond_session_ready.notify_all();
249 yf::Zoom::Impl::Impl()
253 yf::Zoom::Impl::~Impl()
257 void yf::Zoom::Impl::parse_torus(const xmlNode *ptr1)
261 for (ptr1 = ptr1->children; ptr1; ptr1 = ptr1->next)
263 if (ptr1->type != XML_ELEMENT_NODE)
265 if (!strcmp((const char *) ptr1->name, "record"))
267 const xmlNode *ptr2 = ptr1;
268 for (ptr2 = ptr2->children; ptr2; ptr2 = ptr2->next)
270 if (ptr2->type != XML_ELEMENT_NODE)
272 if (!strcmp((const char *) ptr2->name, "layer"))
276 const xmlNode *ptr3 = ptr2;
277 for (ptr3 = ptr3->children; ptr3; ptr3 = ptr3->next)
279 if (ptr3->type != XML_ELEMENT_NODE)
281 if (!strcmp((const char *) ptr3->name, "id"))
283 s.database = mp::xml::get_text(ptr3);
285 else if (!strcmp((const char *) ptr3->name, "zurl"))
287 s.target = mp::xml::get_text(ptr3);
289 else if (!strcmp((const char *) ptr3->name, "sru"))
291 s.sru = mp::xml::get_text(ptr3);
293 else if (!strcmp((const char *) ptr3->name,
296 s.query_encoding = mp::xml::get_text(ptr3);
298 else if (!strcmp((const char *) ptr3->name,
301 s.piggyback = mp::xml::get_bool(ptr3, true);
304 if (s.database.length() && s.target.length())
306 yaz_log(YLOG_LOG, "add db=%s target=%s",
307 s.database.c_str(), s.target.c_str());
308 m_searchables.push_back(s);
317 void yf::Zoom::Impl::configure(const xmlNode *ptr, bool test_only)
319 for (ptr = ptr->children; ptr; ptr = ptr->next)
321 if (ptr->type != XML_ELEMENT_NODE)
323 if (!strcmp((const char *) ptr->name, "records"))
327 else if (!strcmp((const char *) ptr->name, "torus"))
330 const struct _xmlAttr *attr;
331 for (attr = ptr->properties; attr; attr = attr->next)
333 if (!strcmp((const char *) attr->name, "url"))
334 url = mp::xml::get_text(attr->children);
336 throw mp::filter::FilterException(
337 "Bad attribute " + std::string((const char *)
340 torus.read_searchables(url);
341 xmlDoc *doc = torus.get_doc();
344 xmlNode *ptr = xmlDocGetRootElement(doc);
350 throw mp::filter::FilterException
352 + std::string((const char *) ptr->name)
353 + " in zoom filter");
358 yf::Zoom::BackendPtr yf::Zoom::Frontend::get_backend_from_databases(
359 std::string &database, int *error, const char **addinfo)
361 std::list<BackendPtr>::const_iterator map_it;
362 if (m_backend && m_backend->m_frontend_database == database)
365 std::list<Zoom::Searchable>::iterator map_s =
366 m_p->m_searchables.begin();
368 std::string c_db = mp::util::database_name_normalize(database);
370 while (map_s != m_p->m_searchables.end())
372 if (c_db.compare(map_s->database) == 0)
376 if (map_s == m_p->m_searchables.end())
378 *error = YAZ_BIB1_DATABASE_DOES_NOT_EXIST;
379 *addinfo = database.c_str();
386 BackendPtr b(new Backend);
388 b->m_frontend_database = database;
390 if (map_s->query_encoding.length())
391 b->set_option("rpnCharset", map_s->query_encoding.c_str());
394 if (map_s->sru.length())
396 url = "http://" + map_s->target;
397 b->set_option("sru", map_s->sru.c_str());
402 b->connect(url, error, addinfo);
410 Z_Records *yf::Zoom::Frontend::get_records(Odr_int start,
411 Odr_int number_to_present,
413 const char **addinfo,
414 Odr_int *number_of_records_returned,
417 Odr_oid *preferredRecordSyntax,
418 const char *element_set_name)
420 *number_of_records_returned = 0;
421 Z_Records *records = 0;
423 if (start < 0 || number_to_present <= 0)
426 if (number_to_present > 10000)
427 number_to_present = 10000;
429 ZOOM_record *recs = (ZOOM_record *)
430 odr_malloc(odr, number_to_present * sizeof(*recs));
432 char oid_name_str[OID_STR_MAX];
433 const char *syntax_name = 0;
435 if (preferredRecordSyntax)
437 yaz_oid_to_string_buf(preferredRecordSyntax, 0, oid_name_str);
438 b->set_option("preferredRecordSyntax", syntax_name);
440 b->set_option("elementSetName", element_set_name);
442 b->present(start, number_to_present, recs, error, addinfo);
447 for (i = 0; i < number_to_present; i++)
452 { // only return records if no error and at least one record
453 char *odr_database = odr_strdup(odr,
454 b->m_frontend_database.c_str());
455 Z_NamePlusRecordList *npl = (Z_NamePlusRecordList *)
456 odr_malloc(odr, sizeof(*npl));
457 *number_of_records_returned = i;
458 npl->num_records = i;
459 npl->records = (Z_NamePlusRecord **)
460 odr_malloc(odr, i * sizeof(*npl->records));
461 for (i = 0; i < number_to_present; i++)
463 Z_NamePlusRecord *npr = 0;
465 int sur_error = ZOOM_record_error(recs[i], 0 /* msg */,
466 &addinfo, 0 /* diagset */);
470 npr = zget_surrogateDiagRec(odr, odr_database, sur_error,
475 npr = (Z_NamePlusRecord *) odr_malloc(odr, sizeof(*npr));
477 (Z_External *) ZOOM_record_get(recs[i], "ext", 0);
478 npr->databaseName = odr_database;
481 npr->which = Z_NamePlusRecord_databaseRecord;
482 npr->u.databaseRecord = ext;
485 npl->records[i] = npr;
487 records = (Z_Records*) odr_malloc(odr, sizeof(*records));
488 records->which = Z_Records_DBOSD;
489 records->u.databaseOrSurDiagnostics = npl;
495 void yf::Zoom::Frontend::handle_search(mp::Package &package)
497 Z_GDU *gdu = package.request().get();
498 Z_APDU *apdu_req = gdu->u.z3950;
499 Z_APDU *apdu_res = 0;
501 Z_SearchRequest *sr = apdu_req->u.searchRequest;
502 if (sr->num_databaseNames != 1)
504 apdu_res = odr.create_searchResponse(
505 apdu_req, YAZ_BIB1_TOO_MANY_DATABASES_SPECIFIED, 0);
506 package.response() = apdu_res;
511 const char *addinfo = 0;
512 std::string db(sr->databaseNames[0]);
513 BackendPtr b = get_backend_from_databases(db, &error, &addinfo);
517 odr.create_searchResponse(
518 apdu_req, error, addinfo);
519 package.response() = apdu_res;
523 b->set_option("setname", "default");
526 Z_Query *query = sr->query;
527 if (query->which == Z_Query_type_1 || query->which == Z_Query_type_101)
529 WRBUF w = wrbuf_alloc();
530 yaz_rpnquery_to_wrbuf(w, query->u.type_1);
532 b->search_pqf(wrbuf_cstr(w), &hits, &error, &addinfo);
538 odr.create_searchResponse(apdu_req, YAZ_BIB1_QUERY_TYPE_UNSUPP, 0);
539 package.response() = apdu_res;
543 const char *element_set_name = 0;
544 Odr_int number_to_present = 0;
546 mp::util::piggyback_sr(sr, hits, number_to_present, &element_set_name);
548 Odr_int number_of_records_returned = 0;
549 Z_Records *records = get_records(
550 0, number_to_present, &error, &addinfo,
551 &number_of_records_returned, odr, b, sr->preferredRecordSyntax,
553 apdu_res = odr.create_searchResponse(apdu_req, error, addinfo);
556 apdu_res->u.searchResponse->records = records;
557 apdu_res->u.searchResponse->numberOfRecordsReturned =
558 odr_intdup(odr, number_of_records_returned);
560 apdu_res->u.searchResponse->resultCount = odr_intdup(odr, hits);
561 package.response() = apdu_res;
564 void yf::Zoom::Frontend::handle_present(mp::Package &package)
566 Z_GDU *gdu = package.request().get();
567 Z_APDU *apdu_req = gdu->u.z3950;
568 Z_APDU *apdu_res = 0;
569 Z_PresentRequest *pr = apdu_req->u.presentRequest;
574 package.response() = odr.create_presentResponse(
575 apdu_req, YAZ_BIB1_SPECIFIED_RESULT_SET_DOES_NOT_EXIST, 0);
578 const char *element_set_name = 0;
579 Z_RecordComposition *comp = pr->recordComposition;
580 if (comp && comp->which != Z_RecordComp_simple)
582 package.response() = odr.create_presentResponse(
584 YAZ_BIB1_PRESENT_COMP_SPEC_PARAMETER_UNSUPP, 0);
587 if (comp && comp->u.simple->which == Z_ElementSetNames_generic)
588 element_set_name = comp->u.simple->u.generic;
589 Odr_int number_of_records_returned = 0;
591 const char *addinfo = 0;
592 Z_Records *records = get_records(
593 *pr->resultSetStartPoint - 1, *pr->numberOfRecordsRequested,
594 &error, &addinfo, &number_of_records_returned, odr, m_backend,
595 pr->preferredRecordSyntax, element_set_name);
597 apdu_res = odr.create_presentResponse(apdu_req, error, addinfo);
600 apdu_res->u.presentResponse->records = records;
601 apdu_res->u.presentResponse->numberOfRecordsReturned =
602 odr_intdup(odr, number_of_records_returned);
604 package.response() = apdu_res;
607 void yf::Zoom::Frontend::handle_package(mp::Package &package)
609 Z_GDU *gdu = package.request().get();
612 else if (gdu->which == Z_GDU_Z3950)
614 Z_APDU *apdu_req = gdu->u.z3950;
615 if (apdu_req->which == Z_APDU_initRequest)
618 package.response() = odr.create_close(
620 Z_Close_protocolError,
623 else if (apdu_req->which == Z_APDU_searchRequest)
625 handle_search(package);
627 else if (apdu_req->which == Z_APDU_presentRequest)
629 handle_present(package);
634 package.response() = odr.create_close(
636 Z_Close_protocolError,
637 "zoom filter cannot handle this APDU");
638 package.session().close();
643 package.session().close();
647 void yf::Zoom::Impl::process(mp::Package &package)
649 FrontendPtr f = get_frontend(package);
650 Z_GDU *gdu = package.request().get();
654 f->handle_package(package);
656 else if (gdu && gdu->which == Z_GDU_Z3950 && gdu->u.z3950->which ==
659 Z_InitRequest *req = gdu->u.z3950->u.initRequest;
663 Z_APDU *apdu = odr.create_initResponse(gdu->u.z3950, 0, 0);
664 Z_InitResponse *resp = apdu->u.initResponse;
667 static const int masks[] = {
672 for (i = 0; masks[i] != -1; i++)
673 if (ODR_MASK_GET(req->options, masks[i]))
674 ODR_MASK_SET(resp->options, masks[i]);
676 static const int versions[] = {
682 for (i = 0; versions[i] != -1; i++)
683 if (ODR_MASK_GET(req->protocolVersion, versions[i]))
684 ODR_MASK_SET(resp->protocolVersion, versions[i]);
688 *resp->preferredMessageSize = *req->preferredMessageSize;
689 *resp->maximumRecordSize = *req->maximumRecordSize;
691 package.response() = apdu;
692 f->m_is_virtual = true;
697 release_frontend(package);
701 static mp::filter::Base* filter_creator()
703 return new mp::filter::Zoom;
707 struct metaproxy_1_filter_struct metaproxy_1_filter_zoom = {
718 * c-file-style: "Stroustrup"
719 * indent-tabs-mode: nil
721 * vim: shiftwidth=4 tabstop=8 expandtab