1 /* This file is part of Metaproxy.
2 Copyright (C) 2005-2012 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_sort.hpp"
21 #include <metaproxy/package.hpp>
22 #include <metaproxy/util.hpp>
24 #include <yaz/diagbib1.h>
25 #include <yaz/copy_types.h>
27 #include <yaz/oid_std.h>
28 #include <libxml/xpath.h>
29 #include <libxml/xpathInternals.h>
31 #include <boost/thread/mutex.hpp>
32 #include <boost/thread/condition.hpp>
36 namespace mp = metaproxy_1;
37 namespace yf = mp::filter;
39 namespace metaproxy_1 {
42 friend class Frontend;
46 void process(metaproxy_1::Package & package);
47 void configure(const xmlNode * ptr, bool test_only,
51 std::string m_xpath_expr;
52 std::string m_namespaces;
54 boost::condition m_cond_session_ready;
55 std::map<mp::Session, FrontendPtr> m_clients;
56 FrontendPtr get_frontend(mp::Package &package);
57 void release_frontend(mp::Package &package);
60 friend class RecordList;
61 Z_NamePlusRecord *npr;
63 void get_xpath(xmlDoc *doc, const char *namespaces,
65 bool register_namespaces(xmlXPathContextPtr xpathCtx,
68 Record(Z_NamePlusRecord *n, const char *namespaces,
71 bool operator < (const Record &rhs);
73 class Sort::RecordList : boost::noncopyable {
75 std::list<Record> npr_list;
77 std::string namespaces;
78 std::string xpath_expr;
80 void add(Z_NamePlusRecord *s);
81 Z_NamePlusRecord *get(int i);
83 RecordList(Odr_oid *, std::string namespaces,
84 std::string xpath_expr);
87 class Sort::ResultSet : boost::noncopyable {
88 friend class Frontend;
90 std::list<RecordListPtr> record_lists;
92 class Sort::Frontend : boost::noncopyable {
97 std::map<std::string, ResultSetPtr> m_sets;
98 typedef std::map<std::string, ResultSetPtr>::iterator Sets_it;
99 void handle_package(mp::Package &package);
100 void handle_search(mp::Package &package, Z_APDU *apdu_req);
101 void handle_present(mp::Package &package, Z_APDU *apdu_req);
103 void handle_records(mp::Package &package,
109 const char *resultSetId);
111 Frontend(Impl *impl);
117 static void print_xpath_nodes(xmlNodeSetPtr nodes, FILE* output)
124 size = nodes ? nodes->nodeNr : 0;
126 fprintf(output, "Result (%d nodes):\n", size);
127 for (i = 0; i < size; ++i) {
128 assert(nodes->nodeTab[i]);
130 if (nodes->nodeTab[i]->type == XML_NAMESPACE_DECL)
132 xmlNsPtr ns = (xmlNsPtr)nodes->nodeTab[i];
133 cur = (xmlNodePtr)ns->next;
135 fprintf(output, "= namespace \"%s\"=\"%s\" for node %s:%s\n",
136 ns->prefix, ns->href, cur->ns->href, cur->name);
138 fprintf(output, "= namespace \"%s\"=\"%s\" for node %s\n",
139 ns->prefix, ns->href, cur->name);
141 else if (nodes->nodeTab[i]->type == XML_ELEMENT_NODE)
143 cur = nodes->nodeTab[i];
145 fprintf(output, "= element node \"%s:%s\"\n",
146 cur->ns->href, cur->name);
148 fprintf(output, "= element node \"%s\"\n", cur->name);
152 cur = nodes->nodeTab[i];
153 fprintf(output, "= node \"%s\": type %d\n", cur->name, cur->type);
158 bool yf::Sort::Record::register_namespaces(xmlXPathContextPtr xpathCtx,
169 nsListDup = xmlStrdup((const xmlChar *) nsList);
184 next = (xmlChar *) xmlStrchr(next, '=');
194 next = (xmlChar*)xmlStrchr(next, ' ');
198 /* do register namespace */
199 if (xmlXPathRegisterNs(xpathCtx, prefix, href) != 0)
212 void yf::Sort::Record::get_xpath(xmlDoc *doc, const char *namespaces,
215 xmlXPathContextPtr xpathCtx = xmlXPathNewContext(doc);
218 register_namespaces(xpathCtx, namespaces);
219 xmlXPathObjectPtr xpathObj =
220 xmlXPathEvalExpression((const xmlChar *) expr, xpathCtx);
223 print_xpath_nodes(xpathObj->nodesetval, stdout);
225 xmlXPathFreeObject(xpathObj);
227 xmlXPathFreeContext(xpathCtx);
231 yf::Sort::Record::Record(Z_NamePlusRecord *n,
232 const char *namespaces,
233 const char *expr) : npr(n)
235 if (npr->which == Z_NamePlusRecord_databaseRecord)
237 Z_External *ext = npr->u.databaseRecord;
239 if (ext->which == Z_External_octet &&
240 !oid_oidcmp(ext->direct_reference, yaz_oid_recsyn_xml))
242 xmlDoc *doc = xmlParseMemory(
243 (const char *) ext->u.octet_aligned->buf,
244 ext->u.octet_aligned->len);
247 get_xpath(doc, namespaces, expr);
254 yf::Sort::Record::~Record()
258 bool yf::Sort::Record::operator < (const Record &rhs)
261 const char *l_database = this->npr->databaseName;
264 const char *cp = strstr(l_database, ";score=");
266 l_score = atoi(cp + 7);
270 const char *r_database = rhs.npr->databaseName;
273 const char *cp = strstr(r_database, ";score=");
275 r_score = atoi(cp + 7);
278 if (l_score < r_score)
284 yf::Sort::RecordList::RecordList(Odr_oid *syntax,
285 std::string a_namespaces,
286 std::string a_xpath_expr)
287 : namespaces(a_namespaces), xpath_expr(a_xpath_expr)
291 this->syntax = odr_oiddup(m_odr, syntax);
296 yf::Sort::RecordList::~RecordList()
301 void yf::Sort::RecordList::add(Z_NamePlusRecord *s)
304 yaz_log(YLOG_LOG, "Adding to recordList %p", this);
305 Record record(yaz_clone_z_NamePlusRecord(s, oi->mem),
308 npr_list.push_back(record);
311 Z_NamePlusRecord *yf::Sort::RecordList::get(int i)
313 std::list<Record>::const_iterator it = npr_list.begin();
314 for (; it != npr_list.end(); it++, --i)
320 void yf::Sort::RecordList::sort()
325 yf::Sort::Sort() : m_p(new Impl)
330 { // must have a destructor because of boost::scoped_ptr
333 void yf::Sort::configure(const xmlNode *xmlnode, bool test_only,
336 m_p->configure(xmlnode, test_only, path);
339 void yf::Sort::process(mp::Package &package) const
341 m_p->process(package);
345 yf::Sort::Frontend::Frontend(Impl *impl) :
346 m_p(impl), m_is_virtual(false), m_in_use(true)
350 yf::Sort::Frontend::~Frontend()
355 yf::Sort::Impl::Impl() : m_prefetch(20)
359 yf::Sort::Impl::~Impl()
363 yf::Sort::FrontendPtr yf::Sort::Impl::get_frontend(mp::Package &package)
365 boost::mutex::scoped_lock lock(m_mutex);
367 std::map<mp::Session,yf::Sort::FrontendPtr>::iterator it;
371 it = m_clients.find(package.session());
372 if (it == m_clients.end())
375 if (!it->second->m_in_use)
377 it->second->m_in_use = true;
380 m_cond_session_ready.wait(lock);
382 FrontendPtr f(new Frontend(this));
383 m_clients[package.session()] = f;
388 void yf::Sort::Impl::release_frontend(mp::Package &package)
390 boost::mutex::scoped_lock lock(m_mutex);
391 std::map<mp::Session,yf::Sort::FrontendPtr>::iterator it;
393 it = m_clients.find(package.session());
394 if (it != m_clients.end())
396 if (package.session().is_closed())
402 it->second->m_in_use = false;
404 m_cond_session_ready.notify_all();
408 void yf::Sort::Impl::configure(const xmlNode *ptr, bool test_only,
411 for (ptr = ptr->children; ptr; ptr = ptr->next)
413 if (ptr->type != XML_ELEMENT_NODE)
415 if (!strcmp((const char *) ptr->name, "config"))
417 const struct _xmlAttr *attr;
418 for (attr = ptr->properties; attr; attr = attr->next)
420 if (!strcmp((const char *) attr->name, "prefetch"))
422 m_prefetch = mp::xml::get_int(attr->children, -1);
425 throw mp::filter::FilterException(
426 "Bad or missing value for attribute " +
427 std::string((const char *) attr->name));
430 else if (!strcmp((const char *) attr->name, "xpath"))
432 m_xpath_expr = mp::xml::get_text(attr->children);
434 else if (!strcmp((const char *) attr->name, "namespaces"))
436 m_namespaces = mp::xml::get_text(attr->children);
439 throw mp::filter::FilterException(
441 std::string((const char *) attr->name));
446 throw mp::filter::FilterException
448 + std::string((const char *) ptr->name)
449 + " in sort filter");
452 if (m_xpath_expr.length() == 0)
454 throw mp::filter::FilterException
455 ("Missing xpath attribute for config element in sort filter");
460 void yf::Sort::Frontend::handle_records(mp::Package &package,
466 const char *resultSetId)
468 if (records && records->which == Z_Records_DBOSD && start_pos == 1)
470 Z_NamePlusRecordList *nprl = records->u.databaseOrSurDiagnostics;
471 int i; // i is number of records fetched in last response
474 RecordListPtr rlp(new RecordList(syntax,
475 m_p->m_namespaces.c_str(),
476 m_p->m_xpath_expr.c_str()));
477 for (i = 0; i < nprl->num_records; i++, pos++)
478 rlp->add(nprl->records[i]);
480 int end_pos = m_p->m_prefetch;
481 if (end_pos > s->hit_count)
482 end_pos = s->hit_count;
483 while (i && pos <= end_pos)
488 Package present_package(package.session(), package.origin());
489 present_package.copy_filter(package);
491 Z_APDU *p_apdu = zget_APDU(odr, Z_APDU_presentRequest);
492 Z_PresentRequest *p_req = p_apdu->u.presentRequest;
494 *p_req->resultSetStartPoint = pos;
495 *p_req->numberOfRecordsRequested = end_pos - pos + 1;
496 p_req->preferredRecordSyntax = syntax;
497 p_req->resultSetId = odr_strdup(odr, resultSetId);
499 present_package.request() = p_apdu;
500 present_package.move();
502 Z_GDU *gdu_res = present_package.response().get();
503 if (gdu_res && gdu_res->which == Z_GDU_Z3950 &&
504 gdu_res->u.z3950->which == Z_APDU_presentResponse)
506 Z_PresentResponse *res = gdu_res->u.z3950->u.presentResponse;
507 Z_Records *records = res->records;
508 if (records && records->which == Z_Records_DBOSD)
510 Z_NamePlusRecordList *nprl =
511 records->u.databaseOrSurDiagnostics;
512 for (i = 0; i < nprl->num_records; i++, pos++)
513 rlp->add(nprl->records[i]);
517 s->record_lists.push_back(rlp);
520 for (i = 0; i < nprl->num_records; i++)
521 nprl->records[i] = rlp->get(i);
525 void yf::Sort::Frontend::handle_search(mp::Package &package, Z_APDU *apdu_req)
527 Z_SearchRequest *req = apdu_req->u.searchRequest;
528 std::string resultSetId = req->resultSetName;
529 Package b_package(package.session(), package.origin());
532 b_package.copy_filter(package);
533 Sets_it sets_it = m_sets.find(req->resultSetName);
534 if (sets_it != m_sets.end())
536 // result set already exist
537 // if replace indicator is off: we return diagnostic if
538 // result set already exist.
539 if (*req->replaceIndicator == 0)
542 odr.create_searchResponse(
544 YAZ_BIB1_RESULT_SET_EXISTS_AND_REPLACE_INDICATOR_OFF,
546 package.response() = apdu;
549 m_sets.erase(resultSetId);
551 ResultSetPtr s(new ResultSet);
552 m_sets[resultSetId] = s;
554 Z_GDU *gdu_res = package.response().get();
555 if (gdu_res && gdu_res->which == Z_GDU_Z3950 && gdu_res->u.z3950->which ==
556 Z_APDU_searchResponse)
558 Z_SearchResponse *res = gdu_res->u.z3950->u.searchResponse;
559 s->hit_count = *res->resultCount;
560 handle_records(b_package, apdu_req, res->records, 1, s,
561 req->preferredRecordSyntax, resultSetId.c_str());
565 void yf::Sort::Frontend::handle_present(mp::Package &package, Z_APDU *apdu_req)
567 Z_PresentRequest *req = apdu_req->u.presentRequest;
568 std::string resultSetId = req->resultSetId;
569 Package b_package(package.session(), package.origin());
572 b_package.copy_filter(package);
573 Sets_it sets_it = m_sets.find(resultSetId);
574 if (sets_it == m_sets.end())
577 odr.create_presentResponse(
579 YAZ_BIB1_SPECIFIED_RESULT_SET_DOES_NOT_EXIST,
580 resultSetId.c_str());
581 package.response() = apdu;
585 Z_GDU *gdu_res = package.response().get();
586 if (gdu_res && gdu_res->which == Z_GDU_Z3950 && gdu_res->u.z3950->which ==
587 Z_APDU_presentResponse)
589 Z_PresentResponse *res = gdu_res->u.z3950->u.presentResponse;
590 handle_records(b_package, apdu_req, res->records,
591 *req->resultSetStartPoint, sets_it->second,
592 req->preferredRecordSyntax, resultSetId.c_str());
596 void yf::Sort::Frontend::handle_package(mp::Package &package)
598 Z_GDU *gdu = package.request().get();
599 if (gdu && gdu->which == Z_GDU_Z3950)
601 Z_APDU *apdu_req = gdu->u.z3950;
602 switch (apdu_req->which)
604 case Z_APDU_searchRequest:
605 handle_search(package, apdu_req);
607 case Z_APDU_presentRequest:
608 handle_present(package, apdu_req);
615 void yf::Sort::Impl::process(mp::Package &package)
617 FrontendPtr f = get_frontend(package);
618 Z_GDU *gdu = package.request().get();
622 f->handle_package(package);
624 else if (gdu && gdu->which == Z_GDU_Z3950 && gdu->u.z3950->which ==
628 f->m_is_virtual = true;
633 release_frontend(package);
637 static mp::filter::Base* filter_creator()
639 return new mp::filter::Sort;
643 struct metaproxy_1_filter_struct metaproxy_1_filter_sort = {
654 * c-file-style: "Stroustrup"
655 * indent-tabs-mode: nil
657 * vim: shiftwidth=4 tabstop=8 expandtab