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;
55 boost::condition m_cond_session_ready;
56 std::map<mp::Session, FrontendPtr> m_clients;
57 FrontendPtr get_frontend(mp::Package &package);
58 void release_frontend(mp::Package &package);
61 friend class RecordList;
62 Z_NamePlusRecord *npr;
64 void get_xpath(xmlDoc *doc, const char *namespaces,
66 bool register_namespaces(xmlXPathContextPtr xpathCtx,
69 Record(Z_NamePlusRecord *n, const char *namespaces,
72 bool operator < (const Record &rhs);
74 class Sort::RecordList : boost::noncopyable {
76 std::list<Record> npr_list;
78 std::string namespaces;
79 std::string xpath_expr;
81 bool cmp(Odr_oid *syntax);
82 void add(Z_NamePlusRecord *s);
84 Z_NamePlusRecord *get(int i, bool ascending);
86 RecordList(Odr_oid *, std::string namespaces,
87 std::string xpath_expr);
90 class Sort::ResultSet : boost::noncopyable {
91 friend class Frontend;
93 std::list<RecordListPtr> record_lists;
95 class Sort::Frontend : boost::noncopyable {
100 std::map<std::string, ResultSetPtr> m_sets;
101 typedef std::map<std::string, ResultSetPtr>::iterator Sets_it;
102 void handle_package(mp::Package &package);
103 void handle_search(mp::Package &package, Z_APDU *apdu_req);
104 void handle_present(mp::Package &package, Z_APDU *apdu_req);
106 void handle_records(mp::Package &package,
112 const char *resultSetId);
114 Frontend(Impl *impl);
120 static void print_xpath_nodes(xmlNodeSetPtr nodes, FILE* output)
127 size = nodes ? nodes->nodeNr : 0;
129 fprintf(output, "Result (%d nodes):\n", size);
130 for (i = 0; i < size; ++i) {
131 assert(nodes->nodeTab[i]);
133 if (nodes->nodeTab[i]->type == XML_NAMESPACE_DECL)
135 xmlNsPtr ns = (xmlNsPtr)nodes->nodeTab[i];
136 cur = (xmlNodePtr)ns->next;
138 fprintf(output, "= namespace \"%s\"=\"%s\" for node %s:%s\n",
139 ns->prefix, ns->href, cur->ns->href, cur->name);
141 fprintf(output, "= namespace \"%s\"=\"%s\" for node %s\n",
142 ns->prefix, ns->href, cur->name);
144 else if (nodes->nodeTab[i]->type == XML_ELEMENT_NODE)
146 cur = nodes->nodeTab[i];
148 fprintf(output, "= element node \"%s:%s\"\n",
149 cur->ns->href, cur->name);
151 fprintf(output, "= element node \"%s\"\n", cur->name);
155 cur = nodes->nodeTab[i];
156 fprintf(output, "= node \"%s\": type %d\n", cur->name, cur->type);
161 bool yf::Sort::Record::register_namespaces(xmlXPathContextPtr xpathCtx,
172 nsListDup = xmlStrdup((const xmlChar *) nsList);
187 next = (xmlChar *) xmlStrchr(next, '=');
197 next = (xmlChar*)xmlStrchr(next, ' ');
201 /* do register namespace */
202 if (xmlXPathRegisterNs(xpathCtx, prefix, href) != 0)
215 void yf::Sort::Record::get_xpath(xmlDoc *doc, const char *namespaces,
218 xmlXPathContextPtr xpathCtx = xmlXPathNewContext(doc);
221 register_namespaces(xpathCtx, namespaces);
222 xmlXPathObjectPtr xpathObj =
223 xmlXPathEvalExpression((const xmlChar *) expr, xpathCtx);
226 xmlNodeSetPtr nodes = xpathObj->nodesetval;
230 for (i = 0; i < nodes->nodeNr; i++)
233 xmlNode *ptr = nodes->nodeTab[i];
234 if (ptr->type == XML_ELEMENT_NODE ||
235 ptr->type == XML_ATTRIBUTE_NODE)
237 content = mp::xml::get_text(ptr->children);
239 else if (ptr->type == XML_TEXT_NODE)
241 content = mp::xml::get_text(ptr);
250 xmlXPathFreeObject(xpathObj);
252 xmlXPathFreeContext(xpathCtx);
256 yf::Sort::Record::Record(Z_NamePlusRecord *n,
257 const char *namespaces,
258 const char *expr) : npr(n)
260 if (npr->which == Z_NamePlusRecord_databaseRecord)
262 Z_External *ext = npr->u.databaseRecord;
264 if (ext->which == Z_External_octet &&
265 !oid_oidcmp(ext->direct_reference, yaz_oid_recsyn_xml))
267 xmlDoc *doc = xmlParseMemory(
268 (const char *) ext->u.octet_aligned->buf,
269 ext->u.octet_aligned->len);
272 get_xpath(doc, namespaces, expr);
279 yf::Sort::Record::~Record()
283 bool yf::Sort::Record::operator < (const Record &rhs)
285 if (strcmp(this->score.c_str(), rhs.score.c_str()) < 0)
290 yf::Sort::RecordList::RecordList(Odr_oid *syntax,
291 std::string a_namespaces,
292 std::string a_xpath_expr)
293 : namespaces(a_namespaces), xpath_expr(a_xpath_expr)
297 this->syntax = odr_oiddup(m_odr, syntax);
302 yf::Sort::RecordList::~RecordList()
307 bool yf::Sort::RecordList::cmp(Odr_oid *syntax)
309 if ((!this->syntax && !syntax)
311 (this->syntax && syntax && !oid_oidcmp(this->syntax, syntax)))
316 int yf::Sort::RecordList::size()
318 return npr_list.size();
321 void yf::Sort::RecordList::add(Z_NamePlusRecord *s)
324 Z_NamePlusRecord *npr = yaz_clone_z_NamePlusRecord(s, oi->mem);
325 Record record(npr, namespaces.c_str(), xpath_expr.c_str());
326 npr_list.push_back(record);
329 Z_NamePlusRecord *yf::Sort::RecordList::get(int pos, bool ascending)
331 std::list<Record>::const_iterator it = npr_list.begin();
334 i = npr_list.size() - pos - 1;
335 for (; it != npr_list.end(); it++, --i)
343 void yf::Sort::RecordList::sort()
348 yf::Sort::Sort() : m_p(new Impl)
353 { // must have a destructor because of boost::scoped_ptr
356 void yf::Sort::configure(const xmlNode *xmlnode, bool test_only,
359 m_p->configure(xmlnode, test_only, path);
362 void yf::Sort::process(mp::Package &package) const
364 m_p->process(package);
368 yf::Sort::Frontend::Frontend(Impl *impl) :
369 m_p(impl), m_is_virtual(false), m_in_use(true)
373 yf::Sort::Frontend::~Frontend()
378 yf::Sort::Impl::Impl() : m_prefetch(20), m_ascending(true)
382 yf::Sort::Impl::~Impl()
386 yf::Sort::FrontendPtr yf::Sort::Impl::get_frontend(mp::Package &package)
388 boost::mutex::scoped_lock lock(m_mutex);
390 std::map<mp::Session,yf::Sort::FrontendPtr>::iterator it;
394 it = m_clients.find(package.session());
395 if (it == m_clients.end())
398 if (!it->second->m_in_use)
400 it->second->m_in_use = true;
403 m_cond_session_ready.wait(lock);
405 FrontendPtr f(new Frontend(this));
406 m_clients[package.session()] = f;
411 void yf::Sort::Impl::release_frontend(mp::Package &package)
413 boost::mutex::scoped_lock lock(m_mutex);
414 std::map<mp::Session,yf::Sort::FrontendPtr>::iterator it;
416 it = m_clients.find(package.session());
417 if (it != m_clients.end())
419 if (package.session().is_closed())
425 it->second->m_in_use = false;
427 m_cond_session_ready.notify_all();
431 void yf::Sort::Impl::configure(const xmlNode *ptr, bool test_only,
434 for (ptr = ptr->children; ptr; ptr = ptr->next)
436 if (ptr->type != XML_ELEMENT_NODE)
438 if (!strcmp((const char *) ptr->name, "config"))
440 const struct _xmlAttr *attr;
441 for (attr = ptr->properties; attr; attr = attr->next)
443 if (!strcmp((const char *) attr->name, "prefetch"))
445 m_prefetch = mp::xml::get_int(attr->children, -1);
448 throw mp::filter::FilterException(
449 "Bad or missing value for attribute " +
450 std::string((const char *) attr->name));
453 else if (!strcmp((const char *) attr->name, "xpath"))
455 m_xpath_expr = mp::xml::get_text(attr->children);
457 else if (!strcmp((const char *) attr->name, "namespaces"))
459 m_namespaces = mp::xml::get_text(attr->children);
461 else if (!strcmp((const char *) attr->name, "sortorder"))
463 std::string t = mp::xml::get_text(attr->children);
464 if (t == "ascending")
466 else if (t == "descending")
469 throw mp::filter::FilterException(
470 "Bad attribute value " + t + " for attribute " +
471 std::string((const char *) attr->name));
475 throw mp::filter::FilterException(
477 std::string((const char *) attr->name));
482 throw mp::filter::FilterException
484 + std::string((const char *) ptr->name)
485 + " in sort filter");
488 if (m_xpath_expr.length() == 0)
490 throw mp::filter::FilterException
491 ("Missing xpath attribute for config element in sort filter");
496 void yf::Sort::Frontend::handle_records(mp::Package &package,
502 const char *resultSetId)
504 if (records && records->which == Z_Records_DBOSD && start_pos == 1)
506 std::list<RecordListPtr>::const_iterator it = s->record_lists.begin();
508 for (; it != s->record_lists.end(); it++)
509 if ((*it)->cmp(syntax))
512 Z_NamePlusRecordList *nprl = records->u.databaseOrSurDiagnostics;
513 int i; // i is number of records fetched in last response
516 RecordListPtr rlp(new RecordList(syntax,
517 m_p->m_namespaces.c_str(),
518 m_p->m_xpath_expr.c_str()));
519 for (i = 0; i < nprl->num_records; i++, pos++)
520 rlp->add(nprl->records[i]);
522 int end_pos = m_p->m_prefetch;
523 if (end_pos > s->hit_count)
524 end_pos = s->hit_count;
525 while (i && pos <= end_pos)
530 Package present_package(package.session(), package.origin());
531 present_package.copy_filter(package);
533 Z_APDU *p_apdu = zget_APDU(odr, Z_APDU_presentRequest);
534 Z_PresentRequest *p_req = p_apdu->u.presentRequest;
536 *p_req->resultSetStartPoint = pos;
537 *p_req->numberOfRecordsRequested = end_pos - pos + 1;
538 p_req->preferredRecordSyntax = syntax;
539 p_req->resultSetId = odr_strdup(odr, resultSetId);
541 present_package.request() = p_apdu;
542 present_package.move();
544 Z_GDU *gdu_res = present_package.response().get();
545 if (gdu_res && gdu_res->which == Z_GDU_Z3950 &&
546 gdu_res->u.z3950->which == Z_APDU_presentResponse)
548 Z_PresentResponse *res = gdu_res->u.z3950->u.presentResponse;
549 Z_Records *records = res->records;
550 if (records && records->which == Z_Records_DBOSD)
552 Z_NamePlusRecordList *nprl =
553 records->u.databaseOrSurDiagnostics;
554 for (i = 0; i < nprl->num_records; i++, pos++)
555 rlp->add(nprl->records[i]);
559 s->record_lists.push_back(rlp);
562 for (i = 0; i < nprl->num_records; i++)
563 nprl->records[i] = rlp->get(i, m_p->m_ascending);
567 void yf::Sort::Frontend::handle_search(mp::Package &package, Z_APDU *apdu_req)
569 Z_SearchRequest *req = apdu_req->u.searchRequest;
570 std::string resultSetId = req->resultSetName;
571 Package b_package(package.session(), package.origin());
574 b_package.copy_filter(package);
575 Sets_it sets_it = m_sets.find(req->resultSetName);
576 if (sets_it != m_sets.end())
578 // result set already exist
579 // if replace indicator is off: we return diagnostic if
580 // result set already exist.
581 if (*req->replaceIndicator == 0)
584 odr.create_searchResponse(
586 YAZ_BIB1_RESULT_SET_EXISTS_AND_REPLACE_INDICATOR_OFF,
588 package.response() = apdu;
591 m_sets.erase(resultSetId);
593 ResultSetPtr s(new ResultSet);
594 m_sets[resultSetId] = s;
596 Z_GDU *gdu_res = package.response().get();
597 if (gdu_res && gdu_res->which == Z_GDU_Z3950 && gdu_res->u.z3950->which ==
598 Z_APDU_searchResponse)
600 Z_SearchResponse *res = gdu_res->u.z3950->u.searchResponse;
601 s->hit_count = *res->resultCount;
602 handle_records(b_package, apdu_req, res->records, 1, s,
603 req->preferredRecordSyntax, resultSetId.c_str());
604 package.response() = gdu_res;
608 void yf::Sort::Frontend::handle_present(mp::Package &package, Z_APDU *apdu_req)
610 Z_PresentRequest *req = apdu_req->u.presentRequest;
611 std::string resultSetId = req->resultSetId;
612 Package b_package(package.session(), package.origin());
615 b_package.copy_filter(package);
616 Sets_it sets_it = m_sets.find(resultSetId);
617 if (sets_it == m_sets.end())
620 odr.create_presentResponse(
622 YAZ_BIB1_SPECIFIED_RESULT_SET_DOES_NOT_EXIST,
623 resultSetId.c_str());
624 package.response() = apdu;
627 ResultSetPtr rset = sets_it->second;
628 std::list<RecordListPtr>::const_iterator it = rset->record_lists.begin();
629 for (; it != rset->record_lists.end(); it++)
630 if ((*it)->cmp(req->preferredRecordSyntax))
632 if (*req->resultSetStartPoint - 1 + *req->numberOfRecordsRequested
636 Z_APDU *p_apdu = zget_APDU(odr, Z_APDU_presentResponse);
637 Z_PresentResponse *p_res = p_apdu->u.presentResponse;
639 *p_res->nextResultSetPosition = *req->resultSetStartPoint +
640 *req->numberOfRecordsRequested;
641 *p_res->numberOfRecordsReturned =
642 *req->numberOfRecordsRequested;
643 p_res->records = (Z_Records *)
644 odr_malloc(odr, sizeof(*p_res->records));
645 p_res->records->which = Z_Records_DBOSD;
646 Z_NamePlusRecordList *nprl = (Z_NamePlusRecordList *)
647 odr_malloc(odr, sizeof(*nprl));
648 p_res->records->u.databaseOrSurDiagnostics = nprl;
649 nprl->num_records = *req->numberOfRecordsRequested;
650 nprl->records = (Z_NamePlusRecord **)
651 odr_malloc(odr, nprl->num_records * sizeof(*nprl->records));
652 for (i = 0; i < nprl->num_records; i++)
654 int pos = i + *req->resultSetStartPoint - 1;
655 nprl->records[i] = (*it)->get(pos, m_p->m_ascending);
657 package.response() = p_apdu;
663 Z_GDU *gdu_res = package.response().get();
664 if (gdu_res && gdu_res->which == Z_GDU_Z3950 && gdu_res->u.z3950->which ==
665 Z_APDU_presentResponse)
667 Z_PresentResponse *res = gdu_res->u.z3950->u.presentResponse;
668 handle_records(b_package, apdu_req, res->records,
669 *req->resultSetStartPoint, rset,
670 req->preferredRecordSyntax, resultSetId.c_str());
671 package.response() = gdu_res;
675 void yf::Sort::Frontend::handle_package(mp::Package &package)
677 Z_GDU *gdu = package.request().get();
678 if (gdu && gdu->which == Z_GDU_Z3950)
680 Z_APDU *apdu_req = gdu->u.z3950;
681 switch (apdu_req->which)
683 case Z_APDU_searchRequest:
684 handle_search(package, apdu_req);
686 case Z_APDU_presentRequest:
687 handle_present(package, apdu_req);
694 void yf::Sort::Impl::process(mp::Package &package)
696 FrontendPtr f = get_frontend(package);
697 Z_GDU *gdu = package.request().get();
701 f->handle_package(package);
703 else if (gdu && gdu->which == Z_GDU_Z3950 && gdu->u.z3950->which ==
707 f->m_is_virtual = true;
712 release_frontend(package);
716 static mp::filter::Base* filter_creator()
718 return new mp::filter::Sort;
722 struct metaproxy_1_filter_struct metaproxy_1_filter_sort = {
733 * c-file-style: "Stroustrup"
734 * indent-tabs-mode: nil
736 * vim: shiftwidth=4 tabstop=8 expandtab