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 void add(Z_NamePlusRecord *s);
82 Z_NamePlusRecord *get(int i);
84 RecordList(Odr_oid *, std::string namespaces,
85 std::string xpath_expr);
88 class Sort::ResultSet : boost::noncopyable {
89 friend class Frontend;
91 std::list<RecordListPtr> record_lists;
93 class Sort::Frontend : boost::noncopyable {
98 std::map<std::string, ResultSetPtr> m_sets;
99 typedef std::map<std::string, ResultSetPtr>::iterator Sets_it;
100 void handle_package(mp::Package &package);
101 void handle_search(mp::Package &package, Z_APDU *apdu_req);
102 void handle_present(mp::Package &package, Z_APDU *apdu_req);
104 void handle_records(mp::Package &package,
110 const char *resultSetId);
112 Frontend(Impl *impl);
118 static void print_xpath_nodes(xmlNodeSetPtr nodes, FILE* output)
125 size = nodes ? nodes->nodeNr : 0;
127 fprintf(output, "Result (%d nodes):\n", size);
128 for (i = 0; i < size; ++i) {
129 assert(nodes->nodeTab[i]);
131 if (nodes->nodeTab[i]->type == XML_NAMESPACE_DECL)
133 xmlNsPtr ns = (xmlNsPtr)nodes->nodeTab[i];
134 cur = (xmlNodePtr)ns->next;
136 fprintf(output, "= namespace \"%s\"=\"%s\" for node %s:%s\n",
137 ns->prefix, ns->href, cur->ns->href, cur->name);
139 fprintf(output, "= namespace \"%s\"=\"%s\" for node %s\n",
140 ns->prefix, ns->href, cur->name);
142 else if (nodes->nodeTab[i]->type == XML_ELEMENT_NODE)
144 cur = nodes->nodeTab[i];
146 fprintf(output, "= element node \"%s:%s\"\n",
147 cur->ns->href, cur->name);
149 fprintf(output, "= element node \"%s\"\n", cur->name);
153 cur = nodes->nodeTab[i];
154 fprintf(output, "= node \"%s\": type %d\n", cur->name, cur->type);
159 bool yf::Sort::Record::register_namespaces(xmlXPathContextPtr xpathCtx,
170 nsListDup = xmlStrdup((const xmlChar *) nsList);
185 next = (xmlChar *) xmlStrchr(next, '=');
195 next = (xmlChar*)xmlStrchr(next, ' ');
199 /* do register namespace */
200 if (xmlXPathRegisterNs(xpathCtx, prefix, href) != 0)
213 void yf::Sort::Record::get_xpath(xmlDoc *doc, const char *namespaces,
216 xmlXPathContextPtr xpathCtx = xmlXPathNewContext(doc);
219 register_namespaces(xpathCtx, namespaces);
220 xmlXPathObjectPtr xpathObj =
221 xmlXPathEvalExpression((const xmlChar *) expr, xpathCtx);
224 xmlNodeSetPtr nodes = xpathObj->nodesetval;
228 for (i = 0; i < nodes->nodeNr; i++)
231 xmlNode *ptr = nodes->nodeTab[i];
232 if (ptr->type == XML_ELEMENT_NODE ||
233 ptr->type == XML_ATTRIBUTE_NODE)
235 content = mp::xml::get_text(ptr->children);
237 else if (ptr->type == XML_TEXT_NODE)
239 content = mp::xml::get_text(ptr);
248 xmlXPathFreeObject(xpathObj);
250 xmlXPathFreeContext(xpathCtx);
254 yf::Sort::Record::Record(Z_NamePlusRecord *n,
255 const char *namespaces,
256 const char *expr) : npr(n)
258 if (npr->which == Z_NamePlusRecord_databaseRecord)
260 Z_External *ext = npr->u.databaseRecord;
262 if (ext->which == Z_External_octet &&
263 !oid_oidcmp(ext->direct_reference, yaz_oid_recsyn_xml))
265 xmlDoc *doc = xmlParseMemory(
266 (const char *) ext->u.octet_aligned->buf,
267 ext->u.octet_aligned->len);
270 get_xpath(doc, namespaces, expr);
277 yf::Sort::Record::~Record()
281 bool yf::Sort::Record::operator < (const Record &rhs)
283 if (strcmp(this->score.c_str(), rhs.score.c_str()) < 0)
288 yf::Sort::RecordList::RecordList(Odr_oid *syntax,
289 std::string a_namespaces,
290 std::string a_xpath_expr)
291 : namespaces(a_namespaces), xpath_expr(a_xpath_expr)
295 this->syntax = odr_oiddup(m_odr, syntax);
300 yf::Sort::RecordList::~RecordList()
305 void yf::Sort::RecordList::add(Z_NamePlusRecord *s)
308 Record record(yaz_clone_z_NamePlusRecord(s, oi->mem),
311 npr_list.push_back(record);
314 Z_NamePlusRecord *yf::Sort::RecordList::get(int i)
316 std::list<Record>::const_iterator it = npr_list.begin();
317 for (; it != npr_list.end(); it++, --i)
323 void yf::Sort::RecordList::sort()
328 yf::Sort::Sort() : m_p(new Impl)
333 { // must have a destructor because of boost::scoped_ptr
336 void yf::Sort::configure(const xmlNode *xmlnode, bool test_only,
339 m_p->configure(xmlnode, test_only, path);
342 void yf::Sort::process(mp::Package &package) const
344 m_p->process(package);
348 yf::Sort::Frontend::Frontend(Impl *impl) :
349 m_p(impl), m_is_virtual(false), m_in_use(true)
353 yf::Sort::Frontend::~Frontend()
358 yf::Sort::Impl::Impl() : m_prefetch(20), m_ascending(true)
362 yf::Sort::Impl::~Impl()
366 yf::Sort::FrontendPtr yf::Sort::Impl::get_frontend(mp::Package &package)
368 boost::mutex::scoped_lock lock(m_mutex);
370 std::map<mp::Session,yf::Sort::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::Sort::Impl::release_frontend(mp::Package &package)
393 boost::mutex::scoped_lock lock(m_mutex);
394 std::map<mp::Session,yf::Sort::FrontendPtr>::iterator it;
396 it = m_clients.find(package.session());
397 if (it != m_clients.end())
399 if (package.session().is_closed())
405 it->second->m_in_use = false;
407 m_cond_session_ready.notify_all();
411 void yf::Sort::Impl::configure(const xmlNode *ptr, bool test_only,
414 for (ptr = ptr->children; ptr; ptr = ptr->next)
416 if (ptr->type != XML_ELEMENT_NODE)
418 if (!strcmp((const char *) ptr->name, "config"))
420 const struct _xmlAttr *attr;
421 for (attr = ptr->properties; attr; attr = attr->next)
423 if (!strcmp((const char *) attr->name, "prefetch"))
425 m_prefetch = mp::xml::get_int(attr->children, -1);
428 throw mp::filter::FilterException(
429 "Bad or missing value for attribute " +
430 std::string((const char *) attr->name));
433 else if (!strcmp((const char *) attr->name, "xpath"))
435 m_xpath_expr = mp::xml::get_text(attr->children);
437 else if (!strcmp((const char *) attr->name, "namespaces"))
439 m_namespaces = mp::xml::get_text(attr->children);
441 else if (!strcmp((const char *) attr->name, "sortorder"))
443 std::string t = mp::xml::get_text(attr->children);
444 if (t == "ascending")
446 else if (t == "descending")
449 throw mp::filter::FilterException(
450 "Bad attribute value " + t + " for attribute " +
451 std::string((const char *) attr->name));
455 throw mp::filter::FilterException(
457 std::string((const char *) attr->name));
462 throw mp::filter::FilterException
464 + std::string((const char *) ptr->name)
465 + " in sort filter");
468 if (m_xpath_expr.length() == 0)
470 throw mp::filter::FilterException
471 ("Missing xpath attribute for config element in sort filter");
476 void yf::Sort::Frontend::handle_records(mp::Package &package,
482 const char *resultSetId)
484 if (records && records->which == Z_Records_DBOSD && start_pos == 1)
486 Z_NamePlusRecordList *nprl = records->u.databaseOrSurDiagnostics;
487 int i; // i is number of records fetched in last response
490 RecordListPtr rlp(new RecordList(syntax,
491 m_p->m_namespaces.c_str(),
492 m_p->m_xpath_expr.c_str()));
493 for (i = 0; i < nprl->num_records; i++, pos++)
494 rlp->add(nprl->records[i]);
496 int end_pos = m_p->m_prefetch;
497 if (end_pos > s->hit_count)
498 end_pos = s->hit_count;
499 while (i && pos <= end_pos)
504 Package present_package(package.session(), package.origin());
505 present_package.copy_filter(package);
507 Z_APDU *p_apdu = zget_APDU(odr, Z_APDU_presentRequest);
508 Z_PresentRequest *p_req = p_apdu->u.presentRequest;
510 *p_req->resultSetStartPoint = pos;
511 *p_req->numberOfRecordsRequested = end_pos - pos + 1;
512 p_req->preferredRecordSyntax = syntax;
513 p_req->resultSetId = odr_strdup(odr, resultSetId);
515 present_package.request() = p_apdu;
516 present_package.move();
518 Z_GDU *gdu_res = present_package.response().get();
519 if (gdu_res && gdu_res->which == Z_GDU_Z3950 &&
520 gdu_res->u.z3950->which == Z_APDU_presentResponse)
522 Z_PresentResponse *res = gdu_res->u.z3950->u.presentResponse;
523 Z_Records *records = res->records;
524 if (records && records->which == Z_Records_DBOSD)
526 Z_NamePlusRecordList *nprl =
527 records->u.databaseOrSurDiagnostics;
528 for (i = 0; i < nprl->num_records; i++, pos++)
529 rlp->add(nprl->records[i]);
533 s->record_lists.push_back(rlp);
536 for (i = 0; i < nprl->num_records; i++)
537 if (m_p->m_ascending)
538 nprl->records[i] = rlp->get(i);
540 nprl->records[nprl->num_records - i - 1] = rlp->get(i);
544 void yf::Sort::Frontend::handle_search(mp::Package &package, Z_APDU *apdu_req)
546 Z_SearchRequest *req = apdu_req->u.searchRequest;
547 std::string resultSetId = req->resultSetName;
548 Package b_package(package.session(), package.origin());
551 b_package.copy_filter(package);
552 Sets_it sets_it = m_sets.find(req->resultSetName);
553 if (sets_it != m_sets.end())
555 // result set already exist
556 // if replace indicator is off: we return diagnostic if
557 // result set already exist.
558 if (*req->replaceIndicator == 0)
561 odr.create_searchResponse(
563 YAZ_BIB1_RESULT_SET_EXISTS_AND_REPLACE_INDICATOR_OFF,
565 package.response() = apdu;
568 m_sets.erase(resultSetId);
570 ResultSetPtr s(new ResultSet);
571 m_sets[resultSetId] = s;
573 Z_GDU *gdu_res = package.response().get();
574 if (gdu_res && gdu_res->which == Z_GDU_Z3950 && gdu_res->u.z3950->which ==
575 Z_APDU_searchResponse)
577 Z_SearchResponse *res = gdu_res->u.z3950->u.searchResponse;
578 s->hit_count = *res->resultCount;
579 handle_records(b_package, apdu_req, res->records, 1, s,
580 req->preferredRecordSyntax, resultSetId.c_str());
584 void yf::Sort::Frontend::handle_present(mp::Package &package, Z_APDU *apdu_req)
586 Z_PresentRequest *req = apdu_req->u.presentRequest;
587 std::string resultSetId = req->resultSetId;
588 Package b_package(package.session(), package.origin());
591 b_package.copy_filter(package);
592 Sets_it sets_it = m_sets.find(resultSetId);
593 if (sets_it == m_sets.end())
596 odr.create_presentResponse(
598 YAZ_BIB1_SPECIFIED_RESULT_SET_DOES_NOT_EXIST,
599 resultSetId.c_str());
600 package.response() = apdu;
604 Z_GDU *gdu_res = package.response().get();
605 if (gdu_res && gdu_res->which == Z_GDU_Z3950 && gdu_res->u.z3950->which ==
606 Z_APDU_presentResponse)
608 Z_PresentResponse *res = gdu_res->u.z3950->u.presentResponse;
609 handle_records(b_package, apdu_req, res->records,
610 *req->resultSetStartPoint, sets_it->second,
611 req->preferredRecordSyntax, resultSetId.c_str());
615 void yf::Sort::Frontend::handle_package(mp::Package &package)
617 Z_GDU *gdu = package.request().get();
618 if (gdu && gdu->which == Z_GDU_Z3950)
620 Z_APDU *apdu_req = gdu->u.z3950;
621 switch (apdu_req->which)
623 case Z_APDU_searchRequest:
624 handle_search(package, apdu_req);
626 case Z_APDU_presentRequest:
627 handle_present(package, apdu_req);
634 void yf::Sort::Impl::process(mp::Package &package)
636 FrontendPtr f = get_frontend(package);
637 Z_GDU *gdu = package.request().get();
641 f->handle_package(package);
643 else if (gdu && gdu->which == Z_GDU_Z3950 && gdu->u.z3950->which ==
647 f->m_is_virtual = true;
652 release_frontend(package);
656 static mp::filter::Base* filter_creator()
658 return new mp::filter::Sort;
662 struct metaproxy_1_filter_struct metaproxy_1_filter_sort = {
673 * c-file-style: "Stroustrup"
674 * indent-tabs-mode: nil
676 * vim: shiftwidth=4 tabstop=8 expandtab