Further work on sort filter .
[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             boost::mutex m_mutex;
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);
58         };
59         class Sort::Record {
60             friend class RecordList;
61             Z_NamePlusRecord *npr;
62             std::string score;
63             void get_xpath(xmlDoc *doc, const char *namespaces,
64                            const char *expr);
65             bool register_namespaces(xmlXPathContextPtr xpathCtx,
66                                      const char *nsList);
67         public:
68             Record(Z_NamePlusRecord *n, const char *namespaces,
69                    const char *expr);
70             ~Record();
71             bool operator < (const Record &rhs);
72         };
73         class Sort::RecordList : boost::noncopyable {
74             Odr_oid *syntax;
75             std::list<Record> npr_list;
76             mp::odr m_odr;
77             std::string namespaces;
78             std::string xpath_expr;
79         public:
80             void add(Z_NamePlusRecord *s);
81             Z_NamePlusRecord *get(int i);
82             void sort();
83             RecordList(Odr_oid *, std::string namespaces,
84                        std::string xpath_expr);
85             ~RecordList();
86         };
87         class Sort::ResultSet : boost::noncopyable {
88             friend class Frontend;
89             Odr_int hit_count;
90             std::list<RecordListPtr> record_lists;
91         };
92         class Sort::Frontend : boost::noncopyable {
93             friend class Impl;
94             Impl *m_p;
95             bool m_is_virtual;
96             bool m_in_use;
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);
102
103             void handle_records(mp::Package &package,
104                                 Z_APDU *apdu_reqq,
105                                 Z_Records *records,
106                                 Odr_int start_pos,
107                                 ResultSetPtr s,
108                                 Odr_oid *syntax,
109                                 const char *resultSetId);
110         public:
111             Frontend(Impl *impl);
112             ~Frontend();
113         };
114     }
115 }
116
117 static void print_xpath_nodes(xmlNodeSetPtr nodes, FILE* output)
118 {
119     xmlNodePtr cur;
120     int size;
121     int i;
122     
123     assert(output);
124     size = nodes ? nodes->nodeNr : 0;
125     
126     fprintf(output, "Result (%d nodes):\n", size);
127     for (i = 0; i < size; ++i) {
128         assert(nodes->nodeTab[i]);
129         
130         if (nodes->nodeTab[i]->type == XML_NAMESPACE_DECL)
131         {
132             xmlNsPtr ns = (xmlNsPtr)nodes->nodeTab[i];
133             cur = (xmlNodePtr)ns->next;
134             if (cur->ns)
135                 fprintf(output, "= namespace \"%s\"=\"%s\" for node %s:%s\n", 
136                         ns->prefix, ns->href, cur->ns->href, cur->name);
137             else
138                 fprintf(output, "= namespace \"%s\"=\"%s\" for node %s\n", 
139                         ns->prefix, ns->href, cur->name);
140         }
141         else if (nodes->nodeTab[i]->type == XML_ELEMENT_NODE)
142         {
143             cur = nodes->nodeTab[i];        
144             if (cur->ns)
145                 fprintf(output, "= element node \"%s:%s\"\n", 
146                         cur->ns->href, cur->name);
147             else
148                 fprintf(output, "= element node \"%s\"\n",  cur->name);
149         }
150         else
151         {
152             cur = nodes->nodeTab[i];    
153             fprintf(output, "= node \"%s\": type %d\n", cur->name, cur->type);
154         }
155     }
156 }
157
158 bool yf::Sort::Record::register_namespaces(xmlXPathContextPtr xpathCtx,
159                                            const char *nsList)
160 {
161     xmlChar* nsListDup;
162     xmlChar* prefix;
163     xmlChar* href;
164     xmlChar* next;
165     
166     assert(xpathCtx);
167     assert(nsList);
168
169     nsListDup = xmlStrdup((const xmlChar *) nsList);
170     if (!nsListDup)
171         return false;
172     
173     next = nsListDup; 
174     while (next)
175     {
176         /* skip spaces */
177         while (*next == ' ')
178             next++;
179         if (*next == '\0')
180             break;
181
182         /* find prefix */
183         prefix = next;
184         next = (xmlChar *) xmlStrchr(next, '=');
185         if (next == NULL)
186         {
187             xmlFree(nsListDup);
188             return false;
189         }
190         *next++ = '\0'; 
191         
192         /* find href */
193         href = next;
194         next = (xmlChar*)xmlStrchr(next, ' ');
195         if (next)
196             *next++ = '\0';     
197
198         /* do register namespace */
199         if (xmlXPathRegisterNs(xpathCtx, prefix, href) != 0)
200         {
201             xmlFree(nsListDup);
202             return false;
203         }
204     }
205     
206     xmlFree(nsListDup);
207     return true;
208 }
209
210
211
212 void yf::Sort::Record::get_xpath(xmlDoc *doc, const char *namespaces,
213                                  const char *expr)
214 {
215     xmlXPathContextPtr xpathCtx = xmlXPathNewContext(doc);
216     if (xpathCtx)
217     { 
218         register_namespaces(xpathCtx, namespaces);
219         xmlXPathObjectPtr xpathObj =
220             xmlXPathEvalExpression((const xmlChar *) expr, xpathCtx);
221         if (xpathObj)
222         {
223             print_xpath_nodes(xpathObj->nodesetval, stdout);
224
225             xmlXPathFreeObject(xpathObj);
226         }
227         xmlXPathFreeContext(xpathCtx);
228     }
229 }
230
231 yf::Sort::Record::Record(Z_NamePlusRecord *n,
232                          const char *namespaces,
233                          const char *expr) : npr(n) 
234 {
235     if (npr->which == Z_NamePlusRecord_databaseRecord)
236     {
237         Z_External *ext = npr->u.databaseRecord;
238
239         if (ext->which == Z_External_octet &&
240             !oid_oidcmp(ext->direct_reference, yaz_oid_recsyn_xml))
241         {
242             xmlDoc *doc = xmlParseMemory(
243                 (const char *) ext->u.octet_aligned->buf,
244                 ext->u.octet_aligned->len);
245             if (doc)
246             {
247                 get_xpath(doc, namespaces, expr);
248                 xmlFreeDoc(doc);
249             }
250         }
251     }
252 }
253
254 yf::Sort::Record::~Record()
255 {
256 }
257
258 bool yf::Sort::Record::operator < (const Record &rhs)
259 {
260     int l_score = 0;
261     const char *l_database = this->npr->databaseName;
262     if (l_database)
263     {
264         const char *cp = strstr(l_database, ";score=");
265         if (cp)
266             l_score = atoi(cp + 7);
267     }
268
269     int r_score = 0;
270     const char *r_database = rhs.npr->databaseName;
271     if (r_database)
272     {
273         const char *cp = strstr(r_database, ";score=");
274         if (cp)
275             r_score = atoi(cp + 7);
276     }
277     
278     if (l_score < r_score)
279         return true;
280     else
281         return false;
282 }
283
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)
288
289 {
290     if (syntax)
291         this->syntax = odr_oiddup(m_odr, syntax);
292     else
293         this->syntax = 0;
294 }
295
296 yf::Sort::RecordList::~RecordList()
297 {
298     
299 }
300  
301 void yf::Sort::RecordList::add(Z_NamePlusRecord *s)
302 {
303     ODR oi = m_odr;
304     yaz_log(YLOG_LOG, "Adding to recordList %p", this);
305     Record record(yaz_clone_z_NamePlusRecord(s, oi->mem),
306                   namespaces.c_str(),
307                   xpath_expr.c_str());
308     npr_list.push_back(record);
309 }
310
311 Z_NamePlusRecord *yf::Sort::RecordList::get(int i)
312 {
313     std::list<Record>::const_iterator it = npr_list.begin();
314     for (; it != npr_list.end(); it++, --i)
315         if (i <= 0)
316             return it->npr;
317     return 0;
318 }
319
320 void yf::Sort::RecordList::sort()
321 {
322     npr_list.sort();
323 }
324
325 yf::Sort::Sort() : m_p(new Impl)
326 {
327 }
328
329 yf::Sort::~Sort()
330 {  // must have a destructor because of boost::scoped_ptr
331 }
332
333 void yf::Sort::configure(const xmlNode *xmlnode, bool test_only,
334                          const char *path)
335 {
336     m_p->configure(xmlnode, test_only, path);
337 }
338
339 void yf::Sort::process(mp::Package &package) const
340 {
341     m_p->process(package);
342 }
343
344
345 yf::Sort::Frontend::Frontend(Impl *impl) : 
346     m_p(impl), m_is_virtual(false), m_in_use(true)
347 {
348 }
349
350 yf::Sort::Frontend::~Frontend()
351 {
352 }
353
354
355 yf::Sort::Impl::Impl() : m_prefetch(20)
356 {
357 }
358
359 yf::Sort::Impl::~Impl()
360
361 }
362
363 yf::Sort::FrontendPtr yf::Sort::Impl::get_frontend(mp::Package &package)
364 {
365     boost::mutex::scoped_lock lock(m_mutex);
366
367     std::map<mp::Session,yf::Sort::FrontendPtr>::iterator it;
368     
369     while(true)
370     {
371         it = m_clients.find(package.session());
372         if (it == m_clients.end())
373             break;
374         
375         if (!it->second->m_in_use)
376         {
377             it->second->m_in_use = true;
378             return it->second;
379         }
380         m_cond_session_ready.wait(lock);
381     }
382     FrontendPtr f(new Frontend(this));
383     m_clients[package.session()] = f;
384     f->m_in_use = true;
385     return f;
386 }
387
388 void yf::Sort::Impl::release_frontend(mp::Package &package)
389 {
390     boost::mutex::scoped_lock lock(m_mutex);
391     std::map<mp::Session,yf::Sort::FrontendPtr>::iterator it;
392     
393     it = m_clients.find(package.session());
394     if (it != m_clients.end())
395     {
396         if (package.session().is_closed())
397         {
398             m_clients.erase(it);
399         }
400         else
401         {
402             it->second->m_in_use = false;
403         }
404         m_cond_session_ready.notify_all();
405     }
406 }
407
408 void yf::Sort::Impl::configure(const xmlNode *ptr, bool test_only,
409                                const char *path)
410 {
411     for (ptr = ptr->children; ptr; ptr = ptr->next)
412     {
413         if (ptr->type != XML_ELEMENT_NODE)
414             continue;
415         if (!strcmp((const char *) ptr->name, "config"))
416         {            
417             const struct _xmlAttr *attr;
418             for (attr = ptr->properties; attr; attr = attr->next)
419             {
420                 if (!strcmp((const char *) attr->name, "prefetch"))
421                 {
422                     m_prefetch = mp::xml::get_int(attr->children, -1);
423                     if (m_prefetch < 0)
424                     {
425                         throw mp::filter::FilterException(
426                             "Bad or missing value for attribute " + 
427                             std::string((const char *) attr->name));
428                     }
429                 }
430                 else if (!strcmp((const char *) attr->name, "xpath"))
431                 {
432                     m_xpath_expr = mp::xml::get_text(attr->children);
433                 }
434                 else if (!strcmp((const char *) attr->name, "namespaces"))
435                 {
436                     m_namespaces = mp::xml::get_text(attr->children);
437                 }
438                 else
439                     throw mp::filter::FilterException(
440                         "Bad attribute " +
441                         std::string((const char *) attr->name));
442             }
443         }
444         else
445         {
446             throw mp::filter::FilterException
447                 ("Bad element " 
448                  + std::string((const char *) ptr->name)
449                  + " in sort filter");
450         }
451     }
452     if (m_xpath_expr.length() == 0)
453     {
454         throw mp::filter::FilterException
455             ("Missing xpath attribute for config element in sort filter");
456     }
457
458 }
459
460 void yf::Sort::Frontend::handle_records(mp::Package &package,
461                                         Z_APDU *apdu_req,
462                                         Z_Records *records,
463                                         Odr_int start_pos,
464                                         ResultSetPtr s,
465                                         Odr_oid *syntax,
466                                         const char *resultSetId)
467 {
468     if (records && records->which == Z_Records_DBOSD && start_pos == 1)
469     {
470         Z_NamePlusRecordList *nprl = records->u.databaseOrSurDiagnostics;
471         int i;    // i is number of records fetched in last response
472
473         int pos = 1;
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]);
479
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)
484         {
485             mp::odr odr;
486             i = 0;
487
488             Package present_package(package.session(), package.origin());
489             present_package.copy_filter(package);
490
491             Z_APDU *p_apdu = zget_APDU(odr, Z_APDU_presentRequest);
492             Z_PresentRequest *p_req = p_apdu->u.presentRequest;
493
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);
498
499             present_package.request() = p_apdu;
500             present_package.move();
501
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)
505             {
506                 Z_PresentResponse *res = gdu_res->u.z3950->u.presentResponse;
507                 Z_Records *records = res->records;
508                 if (records && records->which == Z_Records_DBOSD)
509                 {
510                     Z_NamePlusRecordList *nprl =
511                         records->u.databaseOrSurDiagnostics;
512                     for (i = 0; i < nprl->num_records; i++, pos++)
513                         rlp->add(nprl->records[i]);
514                 }
515             }
516         }
517         s->record_lists.push_back(rlp);
518         rlp->sort();
519
520         for (i = 0; i < nprl->num_records; i++)
521             nprl->records[i] = rlp->get(i);
522     }
523 }
524
525 void yf::Sort::Frontend::handle_search(mp::Package &package, Z_APDU *apdu_req)
526 {
527     Z_SearchRequest *req = apdu_req->u.searchRequest;    
528     std::string resultSetId = req->resultSetName;
529     Package b_package(package.session(), package.origin());
530     mp::odr odr;
531
532     b_package.copy_filter(package);
533     Sets_it sets_it = m_sets.find(req->resultSetName);
534     if (sets_it != m_sets.end())
535     {
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)
540         {
541             Z_APDU *apdu = 
542                 odr.create_searchResponse(
543                     apdu_req,
544                     YAZ_BIB1_RESULT_SET_EXISTS_AND_REPLACE_INDICATOR_OFF,
545                     0);
546             package.response() = apdu;
547             return;
548         } 
549         m_sets.erase(resultSetId);
550     }
551     ResultSetPtr s(new ResultSet);
552     m_sets[resultSetId] = s;
553     package.move();
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)
557     {
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());
562     }
563 }
564
565 void yf::Sort::Frontend::handle_present(mp::Package &package, Z_APDU *apdu_req)
566 {
567     Z_PresentRequest *req = apdu_req->u.presentRequest;    
568     std::string resultSetId = req->resultSetId;
569     Package b_package(package.session(), package.origin());
570     mp::odr odr;
571
572     b_package.copy_filter(package);
573     Sets_it sets_it = m_sets.find(resultSetId);
574     if (sets_it == m_sets.end())
575     {
576         Z_APDU *apdu = 
577             odr.create_presentResponse(
578                 apdu_req,
579                 YAZ_BIB1_SPECIFIED_RESULT_SET_DOES_NOT_EXIST,
580                 resultSetId.c_str());
581         package.response() = apdu;
582         return;
583     }
584     package.move();
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)
588     {
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());
593     }
594 }
595
596 void yf::Sort::Frontend::handle_package(mp::Package &package)
597 {
598     Z_GDU *gdu = package.request().get();
599     if (gdu && gdu->which == Z_GDU_Z3950)
600     {
601         Z_APDU *apdu_req = gdu->u.z3950;
602         switch (apdu_req->which)
603         {
604         case Z_APDU_searchRequest:
605             handle_search(package, apdu_req);
606             return;
607         case Z_APDU_presentRequest:
608             handle_present(package, apdu_req);
609             return;
610         }
611     }
612     package.move();
613 }
614
615 void yf::Sort::Impl::process(mp::Package &package)
616 {
617     FrontendPtr f = get_frontend(package);
618     Z_GDU *gdu = package.request().get();
619
620     if (f->m_is_virtual)
621     {
622         f->handle_package(package);
623     }
624     else if (gdu && gdu->which == Z_GDU_Z3950 && gdu->u.z3950->which ==
625              Z_APDU_initRequest)
626     {
627         package.move();
628         f->m_is_virtual = true;
629     }
630     else
631         package.move();
632
633     release_frontend(package);
634 }
635
636
637 static mp::filter::Base* filter_creator()
638 {
639     return new mp::filter::Sort;
640 }
641
642 extern "C" {
643     struct metaproxy_1_filter_struct metaproxy_1_filter_sort = {
644         0,
645         "sort",
646         filter_creator
647     };
648 }
649
650
651 /*
652  * Local variables:
653  * c-basic-offset: 4
654  * c-file-style: "Stroustrup"
655  * indent-tabs-mode: nil
656  * End:
657  * vim: shiftwidth=4 tabstop=8 expandtab
658  */
659