Sort module documentation, schema, fixes
[metaproxy-moved-to-github.git] / src / filter_sort.cpp
1 /* This file is part of Metaproxy.
2    Copyright (C) 2005-2012 Index Data
3
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
7 version.
8
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
12 for more details.
13
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
17 */
18
19 #include "config.hpp"
20 #include "filter_sort.hpp"
21 #include <metaproxy/package.hpp>
22 #include <metaproxy/util.hpp>
23
24 #include <yaz/diagbib1.h>
25 #include <yaz/copy_types.h>
26 #include <yaz/log.h>
27 #include <yaz/oid_std.h>
28 #include <libxml/xpath.h>
29 #include <libxml/xpathInternals.h>
30
31 #include <boost/thread/mutex.hpp>
32 #include <boost/thread/condition.hpp>
33
34 #include <yaz/zgdu.h>
35
36 namespace mp = metaproxy_1;
37 namespace yf = mp::filter;
38
39 namespace metaproxy_1 {
40     namespace filter {
41         class Sort::Impl {
42             friend class Frontend;
43         public:
44             Impl();
45             ~Impl();
46             void process(metaproxy_1::Package & package);
47             void configure(const xmlNode * ptr, bool test_only,
48                            const char *path);
49         private:
50             int m_prefetch;
51             std::string m_xpath_expr;
52             std::string m_namespaces;
53             bool m_ascending;
54             bool m_debug;
55             boost::mutex m_mutex;
56             boost::condition m_cond_session_ready;
57             std::map<mp::Session, FrontendPtr> m_clients;
58             FrontendPtr get_frontend(mp::Package &package);
59             void release_frontend(mp::Package &package);
60         };
61         class Sort::Record {
62             friend class RecordList;
63             Z_NamePlusRecord *npr;
64             std::string score;
65             void get_xpath(xmlDoc *doc, const char *namespaces,
66                            const char *expr, bool debug);
67             bool register_namespaces(xmlXPathContextPtr xpathCtx,
68                                      const char *nsList);
69         public:
70             Record(Z_NamePlusRecord *n, const char *namespaces,
71                    const char *expr, bool debug);
72             ~Record();
73             bool operator < (const Record &rhs);
74         };
75         class Sort::RecordList : boost::noncopyable {
76             Odr_oid *syntax;
77             std::list<Record> npr_list;
78             mp::odr m_odr;
79             std::string namespaces;
80             std::string xpath_expr;
81             bool debug;
82         public:
83             bool cmp(Odr_oid *syntax);
84             void add(Z_NamePlusRecord *s);
85             int size();
86             Z_NamePlusRecord *get(int i, bool ascending);
87             void sort();
88             RecordList(Odr_oid *, std::string namespaces,
89                        std::string xpath_expr, bool debug);
90             ~RecordList();
91         };
92         class Sort::ResultSet : boost::noncopyable {
93             friend class Frontend;
94             Odr_int hit_count;
95             std::list<RecordListPtr> record_lists;
96         };
97         class Sort::Frontend : boost::noncopyable {
98             friend class Impl;
99             Impl *m_p;
100             bool m_is_virtual;
101             bool m_in_use;
102             std::map<std::string, ResultSetPtr> m_sets;
103             typedef std::map<std::string, ResultSetPtr>::iterator Sets_it;
104             void handle_package(mp::Package &package);
105             void handle_search(mp::Package &package, Z_APDU *apdu_req);
106             void handle_present(mp::Package &package, Z_APDU *apdu_req);
107
108             void handle_records(mp::Package &package,
109                                 Z_APDU *apdu_reqq,
110                                 Z_Records *records,
111                                 Odr_int start_pos,
112                                 ResultSetPtr s,
113                                 Odr_oid *syntax,
114                                 const char *resultSetId);
115         public:
116             Frontend(Impl *impl);
117             ~Frontend();
118         };
119     }
120 }
121
122 static void print_xpath_nodes(xmlNodeSetPtr nodes, FILE* output)
123 {
124     xmlNodePtr cur;
125     int size;
126     int i;
127     
128     assert(output);
129     size = nodes ? nodes->nodeNr : 0;
130     
131     fprintf(output, "Result (%d nodes):\n", size);
132     for (i = 0; i < size; ++i) {
133         assert(nodes->nodeTab[i]);
134         
135         if (nodes->nodeTab[i]->type == XML_NAMESPACE_DECL)
136         {
137             xmlNsPtr ns = (xmlNsPtr)nodes->nodeTab[i];
138             cur = (xmlNodePtr)ns->next;
139             if (cur->ns)
140                 fprintf(output, "= namespace \"%s\"=\"%s\" for node %s:%s\n", 
141                         ns->prefix, ns->href, cur->ns->href, cur->name);
142             else
143                 fprintf(output, "= namespace \"%s\"=\"%s\" for node %s\n", 
144                         ns->prefix, ns->href, cur->name);
145         }
146         else if (nodes->nodeTab[i]->type == XML_ELEMENT_NODE)
147         {
148             cur = nodes->nodeTab[i];        
149             if (cur->ns)
150                 fprintf(output, "= element node \"%s:%s\"\n", 
151                         cur->ns->href, cur->name);
152             else
153                 fprintf(output, "= element node \"%s\"\n",  cur->name);
154         }
155         else
156         {
157             cur = nodes->nodeTab[i];    
158             fprintf(output, "= node \"%s\": type %d\n", cur->name, cur->type);
159         }
160     }
161 }
162
163 bool yf::Sort::Record::register_namespaces(xmlXPathContextPtr xpathCtx,
164                                            const char *nsList)
165 {
166     xmlChar* nsListDup;
167     xmlChar* prefix;
168     xmlChar* href;
169     xmlChar* next;
170     
171     assert(xpathCtx);
172     assert(nsList);
173
174     nsListDup = xmlStrdup((const xmlChar *) nsList);
175     if (!nsListDup)
176         return false;
177     
178     next = nsListDup; 
179     while (next)
180     {
181         /* skip spaces */
182         while (*next == ' ')
183             next++;
184         if (*next == '\0')
185             break;
186
187         /* find prefix */
188         prefix = next;
189         next = (xmlChar *) xmlStrchr(next, '=');
190         if (next == NULL)
191         {
192             xmlFree(nsListDup);
193             return false;
194         }
195         *next++ = '\0'; 
196         
197         /* find href */
198         href = next;
199         next = (xmlChar*)xmlStrchr(next, ' ');
200         if (next)
201             *next++ = '\0';     
202
203         /* do register namespace */
204         if (xmlXPathRegisterNs(xpathCtx, prefix, href) != 0)
205         {
206             xmlFree(nsListDup);
207             return false;
208         }
209     }
210     
211     xmlFree(nsListDup);
212     return true;
213 }
214
215
216
217 void yf::Sort::Record::get_xpath(xmlDoc *doc, const char *namespaces,
218                                  const char *expr, bool debug)
219 {
220     xmlXPathContextPtr xpathCtx = xmlXPathNewContext(doc);
221     if (xpathCtx)
222     { 
223         register_namespaces(xpathCtx, namespaces);
224         xmlXPathObjectPtr xpathObj =
225             xmlXPathEvalExpression((const xmlChar *) expr, xpathCtx);
226         if (xpathObj)
227         {
228             xmlNodeSetPtr nodes = xpathObj->nodesetval;
229             if (debug)
230                 print_xpath_nodes(nodes, yaz_log_file());
231             if (nodes)
232             {
233                 int i;
234                 for (i = 0; i < nodes->nodeNr; i++)
235                 {
236                     std::string content;
237                     xmlNode *ptr = nodes->nodeTab[i];
238                     if (ptr->type == XML_ELEMENT_NODE ||
239                         ptr->type == XML_ATTRIBUTE_NODE)
240                     {
241                         content = mp::xml::get_text(ptr->children);
242                     }
243                     else if (ptr->type == XML_TEXT_NODE)
244                     {
245                         content = mp::xml::get_text(ptr);
246                     }
247                     if (content.length())
248                     {
249                         score = content;
250                         break;
251                     }
252                 }
253             }
254             xmlXPathFreeObject(xpathObj);
255         }
256         xmlXPathFreeContext(xpathCtx);
257     }
258 }
259
260 yf::Sort::Record::Record(Z_NamePlusRecord *n,
261                          const char *namespaces,
262                          const char *expr,
263                          bool debug) : npr(n) 
264 {
265     if (npr->which == Z_NamePlusRecord_databaseRecord)
266     {
267         Z_External *ext = npr->u.databaseRecord;
268
269         if (ext->which == Z_External_octet &&
270             !oid_oidcmp(ext->direct_reference, yaz_oid_recsyn_xml))
271         {
272             xmlDoc *doc = xmlParseMemory(
273                 (const char *) ext->u.octet_aligned->buf,
274                 ext->u.octet_aligned->len);
275             if (doc)
276             {
277                 get_xpath(doc, namespaces, expr, debug);
278                 xmlFreeDoc(doc);
279             }
280         }
281     }
282 }
283
284 yf::Sort::Record::~Record()
285 {
286 }
287
288 bool yf::Sort::Record::operator < (const Record &rhs)
289 {
290     if (strcmp(this->score.c_str(), rhs.score.c_str()) < 0)
291         return true;
292     return false;
293 }
294
295 yf::Sort::RecordList::RecordList(Odr_oid *syntax,
296                                  std::string a_namespaces,
297                                  std::string a_xpath_expr,
298                                  bool a_debug)
299     : namespaces(a_namespaces), xpath_expr(a_xpath_expr), debug(a_debug)
300
301 {
302     if (syntax)
303         this->syntax = odr_oiddup(m_odr, syntax);
304     else
305         this->syntax = 0;
306 }
307
308 yf::Sort::RecordList::~RecordList()
309 {
310     
311 }
312  
313 bool yf::Sort::RecordList::cmp(Odr_oid *syntax)
314 {
315     if ((!this->syntax && !syntax)
316         ||
317         (this->syntax && syntax && !oid_oidcmp(this->syntax, syntax)))
318         return true;
319     return false;
320 }
321
322 int yf::Sort::RecordList::size()
323 {
324     return npr_list.size();
325 }
326
327 void yf::Sort::RecordList::add(Z_NamePlusRecord *s)
328 {
329     ODR oi = m_odr;
330     Z_NamePlusRecord *npr = yaz_clone_z_NamePlusRecord(s, oi->mem);
331     Record record(npr, namespaces.c_str(), xpath_expr.c_str(), debug);
332     npr_list.push_back(record);
333 }
334
335 Z_NamePlusRecord *yf::Sort::RecordList::get(int pos, bool ascending)
336 {
337     std::list<Record>::const_iterator it = npr_list.begin();
338     int i = pos;
339     if (!ascending)
340         i = npr_list.size() - pos - 1;
341     for (; it != npr_list.end(); it++, --i)
342         if (i <= 0)
343         {
344             return it->npr;
345         }
346     return 0;
347 }
348
349 void yf::Sort::RecordList::sort()
350 {
351     npr_list.sort();
352 }
353
354 yf::Sort::Sort() : m_p(new Impl)
355 {
356 }
357
358 yf::Sort::~Sort()
359 {  // must have a destructor because of boost::scoped_ptr
360 }
361
362 void yf::Sort::configure(const xmlNode *xmlnode, bool test_only,
363                          const char *path)
364 {
365     m_p->configure(xmlnode, test_only, path);
366 }
367
368 void yf::Sort::process(mp::Package &package) const
369 {
370     m_p->process(package);
371 }
372
373
374 yf::Sort::Frontend::Frontend(Impl *impl) : 
375     m_p(impl), m_is_virtual(false), m_in_use(true)
376 {
377 }
378
379 yf::Sort::Frontend::~Frontend()
380 {
381 }
382
383
384 yf::Sort::Impl::Impl() : m_prefetch(20), m_ascending(true), m_debug(false)
385 {
386 }
387
388 yf::Sort::Impl::~Impl()
389
390 }
391
392 yf::Sort::FrontendPtr yf::Sort::Impl::get_frontend(mp::Package &package)
393 {
394     boost::mutex::scoped_lock lock(m_mutex);
395
396     std::map<mp::Session,yf::Sort::FrontendPtr>::iterator it;
397     
398     while(true)
399     {
400         it = m_clients.find(package.session());
401         if (it == m_clients.end())
402             break;
403         
404         if (!it->second->m_in_use)
405         {
406             it->second->m_in_use = true;
407             return it->second;
408         }
409         m_cond_session_ready.wait(lock);
410     }
411     FrontendPtr f(new Frontend(this));
412     m_clients[package.session()] = f;
413     f->m_in_use = true;
414     return f;
415 }
416
417 void yf::Sort::Impl::release_frontend(mp::Package &package)
418 {
419     boost::mutex::scoped_lock lock(m_mutex);
420     std::map<mp::Session,yf::Sort::FrontendPtr>::iterator it;
421     
422     it = m_clients.find(package.session());
423     if (it != m_clients.end())
424     {
425         if (package.session().is_closed())
426         {
427             m_clients.erase(it);
428         }
429         else
430         {
431             it->second->m_in_use = false;
432         }
433         m_cond_session_ready.notify_all();
434     }
435 }
436
437 void yf::Sort::Impl::configure(const xmlNode *ptr, bool test_only,
438                                const char *path)
439 {
440     for (ptr = ptr->children; ptr; ptr = ptr->next)
441     {
442         if (ptr->type != XML_ELEMENT_NODE)
443             continue;
444         if (!strcmp((const char *) ptr->name, "sort"))
445         {            
446             const struct _xmlAttr *attr;
447             for (attr = ptr->properties; attr; attr = attr->next)
448             {
449                 if (!strcmp((const char *) attr->name, "prefetch"))
450                 {
451                     m_prefetch = mp::xml::get_int(attr->children, -1);
452                     if (m_prefetch < 0)
453                     {
454                         throw mp::filter::FilterException(
455                             "Bad or missing value for attribute " + 
456                             std::string((const char *) attr->name));
457                     }
458                 }
459                 else if (!strcmp((const char *) attr->name, "xpath"))
460                 {
461                     m_xpath_expr = mp::xml::get_text(attr->children);
462                 }
463                 else if (!strcmp((const char *) attr->name, "namespaces"))
464                 {
465                     m_namespaces = mp::xml::get_text(attr->children);
466                 }
467                 else if (!strcmp((const char *) attr->name, "ascending"))
468                 {
469                     m_ascending = mp::xml::get_bool(attr->children, true);
470                 }
471                 else if (!strcmp((const char *) attr->name, "debug"))
472                 {
473                     m_debug = mp::xml::get_bool(attr->children, false);
474                 }
475                 else
476                     throw mp::filter::FilterException(
477                         "Bad attribute " +
478                         std::string((const char *) attr->name));
479             }
480         }
481         else
482         {
483             throw mp::filter::FilterException
484                 ("Bad element " 
485                  + std::string((const char *) ptr->name)
486                  + " in sort filter");
487         }
488     }
489     if (m_xpath_expr.length() == 0)
490     {
491         throw mp::filter::FilterException
492             ("Missing xpath attribute for config element in sort filter");
493     }
494
495 }
496
497 void yf::Sort::Frontend::handle_records(mp::Package &package,
498                                         Z_APDU *apdu_req,
499                                         Z_Records *records,
500                                         Odr_int start_pos,
501                                         ResultSetPtr s,
502                                         Odr_oid *syntax,
503                                         const char *resultSetId)
504 {
505     if (records && records->which == Z_Records_DBOSD && start_pos == 1)
506     {
507         std::list<RecordListPtr>::const_iterator it = s->record_lists.begin();
508
509         for (; it != s->record_lists.end(); it++)
510             if ((*it)->cmp(syntax))
511                 return;
512
513         Z_NamePlusRecordList *nprl = records->u.databaseOrSurDiagnostics;
514         int i;    // i is number of records fetched in last response
515
516         int pos = 1;
517         RecordListPtr rlp(new RecordList(syntax,
518                                          m_p->m_namespaces.c_str(),
519                                          m_p->m_xpath_expr.c_str(),
520                                          m_p->m_debug));
521         for (i = 0; i < nprl->num_records; i++, pos++)
522             rlp->add(nprl->records[i]);
523
524         int end_pos = m_p->m_prefetch;
525         if (end_pos > s->hit_count)
526             end_pos = s->hit_count;
527         while (i && pos <= end_pos)
528         {
529             mp::odr odr;
530             i = 0;
531
532             Package present_package(package.session(), package.origin());
533             present_package.copy_filter(package);
534
535             Z_APDU *p_apdu = zget_APDU(odr, Z_APDU_presentRequest);
536             Z_PresentRequest *p_req = p_apdu->u.presentRequest;
537
538             *p_req->resultSetStartPoint = pos;
539             *p_req->numberOfRecordsRequested = end_pos - pos + 1;
540             p_req->preferredRecordSyntax = syntax;
541             p_req->resultSetId = odr_strdup(odr, resultSetId);
542
543             present_package.request() = p_apdu;
544             present_package.move();
545
546             Z_GDU *gdu_res = present_package.response().get();
547             if (gdu_res && gdu_res->which == Z_GDU_Z3950 &&
548                 gdu_res->u.z3950->which == Z_APDU_presentResponse)
549             {
550                 Z_PresentResponse *res = gdu_res->u.z3950->u.presentResponse;
551                 Z_Records *records = res->records;
552                 if (records && records->which == Z_Records_DBOSD)
553                 {
554                     Z_NamePlusRecordList *nprl =
555                         records->u.databaseOrSurDiagnostics;
556                     for (i = 0; i < nprl->num_records; i++, pos++)
557                         rlp->add(nprl->records[i]);
558                 }
559             }
560         }
561         s->record_lists.push_back(rlp);
562         rlp->sort();
563
564         for (i = 0; i < nprl->num_records; i++)
565             nprl->records[i] = rlp->get(i, m_p->m_ascending);
566     }
567 }
568
569 void yf::Sort::Frontend::handle_search(mp::Package &package, Z_APDU *apdu_req)
570 {
571     Z_SearchRequest *req = apdu_req->u.searchRequest;    
572     std::string resultSetId = req->resultSetName;
573     Package b_package(package.session(), package.origin());
574     mp::odr odr;
575     Odr_oid *syntax = 0;
576
577     if (req->preferredRecordSyntax)
578         syntax = odr_oiddup(odr, req->preferredRecordSyntax);
579
580     b_package.copy_filter(package);
581     Sets_it sets_it = m_sets.find(req->resultSetName);
582     if (sets_it != m_sets.end())
583     {
584         // result set already exist 
585         // if replace indicator is off: we return diagnostic if
586         // result set already exist.
587         if (*req->replaceIndicator == 0)
588         {
589             Z_APDU *apdu = 
590                 odr.create_searchResponse(
591                     apdu_req,
592                     YAZ_BIB1_RESULT_SET_EXISTS_AND_REPLACE_INDICATOR_OFF,
593                     0);
594             package.response() = apdu;
595             return;
596         } 
597         m_sets.erase(resultSetId);
598     }
599     ResultSetPtr s(new ResultSet);
600     m_sets[resultSetId] = s;
601     package.move();
602     Z_GDU *gdu_res = package.response().get();
603     if (gdu_res && gdu_res->which == Z_GDU_Z3950 && gdu_res->u.z3950->which ==
604         Z_APDU_searchResponse)
605     {
606         Z_SearchResponse *res = gdu_res->u.z3950->u.searchResponse;
607         s->hit_count = *res->resultCount;
608         handle_records(b_package, apdu_req, res->records, 1, s,
609                        syntax, resultSetId.c_str());
610         package.response() = gdu_res;
611     }
612 }
613
614 void yf::Sort::Frontend::handle_present(mp::Package &package, Z_APDU *apdu_req)
615 {
616     Z_PresentRequest *req = apdu_req->u.presentRequest;    
617     std::string resultSetId = req->resultSetId;
618     Package b_package(package.session(), package.origin());
619     mp::odr odr;
620     Odr_oid *syntax = 0;
621     Odr_int start = *req->resultSetStartPoint;
622
623     if (req->preferredRecordSyntax)
624         syntax = odr_oiddup(odr, req->preferredRecordSyntax);
625
626     b_package.copy_filter(package);
627     Sets_it sets_it = m_sets.find(resultSetId);
628     if (sets_it == m_sets.end())
629     {
630         Z_APDU *apdu = 
631             odr.create_presentResponse(
632                 apdu_req,
633                 YAZ_BIB1_SPECIFIED_RESULT_SET_DOES_NOT_EXIST,
634                 resultSetId.c_str());
635         package.response() = apdu;
636         return;
637     }
638     ResultSetPtr rset = sets_it->second;
639     std::list<RecordListPtr>::const_iterator it = rset->record_lists.begin();
640     for (; it != rset->record_lists.end(); it++)
641         if ((*it)->cmp(req->preferredRecordSyntax))
642         {
643             if (*req->resultSetStartPoint - 1 + *req->numberOfRecordsRequested 
644                 <= (*it)->size())
645             {
646                 int i;
647                 Z_APDU *p_apdu = zget_APDU(odr, Z_APDU_presentResponse);
648                 Z_PresentResponse *p_res = p_apdu->u.presentResponse;
649
650                 *p_res->nextResultSetPosition = *req->resultSetStartPoint +
651                     *req->numberOfRecordsRequested;
652                 *p_res->numberOfRecordsReturned = 
653                     *req->numberOfRecordsRequested;
654                 p_res->records = (Z_Records *) 
655                     odr_malloc(odr, sizeof(*p_res->records));
656                 p_res->records->which = Z_Records_DBOSD;
657                 Z_NamePlusRecordList *nprl =  (Z_NamePlusRecordList *)
658                     odr_malloc(odr, sizeof(*nprl));
659                 p_res->records->u.databaseOrSurDiagnostics = nprl;
660                 nprl->num_records = *req->numberOfRecordsRequested;
661                 nprl->records = (Z_NamePlusRecord **)
662                     odr_malloc(odr, nprl->num_records * sizeof(*nprl->records));
663                 for (i = 0; i < nprl->num_records; i++)
664                 {
665                     int pos = i + *req->resultSetStartPoint - 1;
666                     nprl->records[i] = (*it)->get(pos, m_p->m_ascending);
667                 }
668                 package.response() = p_apdu;
669                 return;
670             }
671             break;
672         }
673     package.move();
674     Z_GDU *gdu_res = package.response().get();
675     if (gdu_res && gdu_res->which == Z_GDU_Z3950 && gdu_res->u.z3950->which ==
676         Z_APDU_presentResponse)
677     {
678         Z_PresentResponse *res = gdu_res->u.z3950->u.presentResponse;
679         handle_records(b_package, apdu_req, res->records, 
680                        start, rset, syntax, resultSetId.c_str());
681         package.response() = gdu_res;
682     }
683 }
684
685 void yf::Sort::Frontend::handle_package(mp::Package &package)
686 {
687     Z_GDU *gdu = package.request().get();
688     if (gdu && gdu->which == Z_GDU_Z3950)
689     {
690         Z_APDU *apdu_req = gdu->u.z3950;
691         switch (apdu_req->which)
692         {
693         case Z_APDU_searchRequest:
694             handle_search(package, apdu_req);
695             return;
696         case Z_APDU_presentRequest:
697             handle_present(package, apdu_req);
698             return;
699         }
700     }
701     package.move();
702 }
703
704 void yf::Sort::Impl::process(mp::Package &package)
705 {
706     FrontendPtr f = get_frontend(package);
707     Z_GDU *gdu = package.request().get();
708
709     if (f->m_is_virtual)
710     {
711         f->handle_package(package);
712     }
713     else if (gdu && gdu->which == Z_GDU_Z3950 && gdu->u.z3950->which ==
714              Z_APDU_initRequest)
715     {
716         package.move();
717         f->m_is_virtual = true;
718     }
719     else
720         package.move();
721
722     release_frontend(package);
723 }
724
725
726 static mp::filter::Base* filter_creator()
727 {
728     return new mp::filter::Sort;
729 }
730
731 extern "C" {
732     struct metaproxy_1_filter_struct metaproxy_1_filter_sort = {
733         0,
734         "sort",
735         filter_creator
736     };
737 }
738
739
740 /*
741  * Local variables:
742  * c-basic-offset: 4
743  * c-file-style: "Stroustrup"
744  * indent-tabs-mode: nil
745  * End:
746  * vim: shiftwidth=4 tabstop=8 expandtab
747  */
748