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