Add note about C++11 tweak
[mp-xquery-moved-to-github.git] / src / metaproxy_filter_xquery.cpp
1 /* This file is part of mp-xquery
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 <metaproxy/package.hpp>
20 #include <metaproxy/util.hpp>
21 #include <yaz/log.h>
22 #include <yaz/oid_db.h>
23 #include <yaz/diagbib1.h>
24 #include <map>
25 #include <string>
26 #include <iostream>
27 #include <fstream>
28 #include <sstream>
29
30 #include <zorba/zorba.h>
31 #include <zorba/store_manager.h>
32 #include <zorba/serializer.h>
33 #include <zorba/singleton_item_sequence.h>
34 #include <zorba/zorba_exception.h>
35
36
37 namespace mp = metaproxy_1;
38 namespace yf = mp::filter;
39 namespace mp_util = metaproxy_1::util;
40 using namespace mp;
41 using namespace zorba;
42
43 namespace metaproxy_1 {
44     namespace filter {
45         class XQuery : public Base {
46         public:
47             ~XQuery();
48             XQuery();
49             void process(metaproxy_1::Package & package) const;
50             void configure(const xmlNode * ptr, bool test_only,
51                            const char *path);
52             void start() const;
53             void stop(int signo) const;
54         private:
55             bool convert_one_record(const char *input_buf,
56                                     size_t input_len,
57                                     std::string &result) const;
58             std::map<std::string, std::string> zorba_variables;
59             std::string zorba_filename;
60             std::string zorba_script;
61             std::string zorba_record_variable;
62             std::string elementset_input;
63             std::string elementset_output;
64             Zorba *lZorba;
65             XQuery_t lQuery;
66         };
67     }
68 }
69
70 yf::XQuery::XQuery()
71 {
72     lZorba = 0;
73 }
74
75 yf::XQuery::~XQuery()
76 {
77     if (lZorba)
78         lZorba->shutdown();
79 }
80
81 void yf::XQuery::start() const
82 {
83 }
84
85 void yf::XQuery::stop(int signo) const
86 {
87 }
88
89 bool yf::XQuery::convert_one_record(const char *input_buf,
90                                     size_t input_len,
91                                     std::string &result) const
92 {
93     XQuery_t tQuery = lQuery->clone();
94
95     zorba::DynamicContext* lDynamicContext = tQuery->getDynamicContext();
96
97     zorba::Item lItem;
98     std::map<std::string, std::string>::const_iterator it;
99     for (it = zorba_variables.begin(); it != zorba_variables.end(); it++)
100     {
101         lItem = lZorba->getItemFactory()->createString(it->second);
102         lDynamicContext->setVariable(it->first, lItem);
103     }
104     std::string rec_content = "raw:" + std::string(input_buf, input_len);
105     lItem = lZorba->getItemFactory()->createString(rec_content);
106     lDynamicContext->setVariable(zorba_record_variable, lItem);
107
108     try {
109         std::stringstream ss;
110         tQuery->execute(ss);
111         result = ss.str();
112         return true;
113     } catch ( ZorbaException &e) {
114         result = e.what();
115         yaz_log(YLOG_WARN, "XQuery execute: %s", result.c_str());
116         return false;
117     }
118 }
119
120 void yf::XQuery::process(Package &package) const
121 {
122     Z_GDU *gdu_req = package.request().get();
123     Z_PresentRequest *pr_req = 0;
124     Z_SearchRequest *sr_req = 0;
125
126     const char *input_schema = 0;
127     Odr_oid *input_syntax = 0;
128
129     if (gdu_req && gdu_req->which == Z_GDU_Z3950 &&
130         gdu_req->u.z3950->which == Z_APDU_presentRequest)
131     {
132         pr_req = gdu_req->u.z3950->u.presentRequest;
133
134         input_schema =
135             mp_util::record_composition_to_esn(pr_req->recordComposition);
136         input_syntax = pr_req->preferredRecordSyntax;
137     }
138     else if (gdu_req && gdu_req->which == Z_GDU_Z3950 &&
139              gdu_req->u.z3950->which == Z_APDU_searchRequest)
140     {
141         sr_req = gdu_req->u.z3950->u.searchRequest;
142
143         input_syntax = sr_req->preferredRecordSyntax;
144
145         // we don't know how many hits we're going to get and therefore
146         // the effective element set name.. Therefore we can only allow
147         // two cases.. Both equal or absent.. If not, we'll just have to
148         // disable the piggyback!
149         if (sr_req->smallSetElementSetNames
150             &&
151             sr_req->mediumSetElementSetNames
152             &&
153             sr_req->smallSetElementSetNames->which == Z_ElementSetNames_generic
154             &&
155             sr_req->mediumSetElementSetNames->which == Z_ElementSetNames_generic
156             &&
157             !strcmp(sr_req->smallSetElementSetNames->u.generic,
158                     sr_req->mediumSetElementSetNames->u.generic))
159         {
160             input_schema = sr_req->smallSetElementSetNames->u.generic;
161         }
162         else if (!sr_req->smallSetElementSetNames &&
163                  !sr_req->mediumSetElementSetNames)
164             ; // input_schema is 0 already
165         else
166         {
167             // disable piggyback (perhaps it was disabled already)
168             *sr_req->smallSetUpperBound = 0;
169             *sr_req->largeSetLowerBound = 0;
170             *sr_req->mediumSetPresentNumber = 0;
171             package.move();
172             return;
173         }
174         // we can handle it in record_transform.
175     }
176     else
177     {
178         package.move();
179         return;
180     }
181
182     mp::odr odr_en(ODR_ENCODE);
183
184     const char *backend_schema = 0;
185     const Odr_oid *backend_syntax = 0;
186
187     if (input_schema && !strcmp(input_schema, elementset_input.c_str()) &&
188         (!input_syntax || !oid_oidcmp(input_syntax, yaz_oid_recsyn_xml)))
189     {
190         backend_schema = elementset_output.c_str();
191         backend_syntax = yaz_oid_recsyn_xml;
192     }
193     else
194     {
195         package.move();
196         return;
197     }
198
199     if (sr_req)
200     {
201         if (backend_syntax)
202             sr_req->preferredRecordSyntax = odr_oiddup(odr_en, backend_syntax);
203         else
204             sr_req->preferredRecordSyntax = 0;
205         if (backend_schema)
206         {
207             sr_req->smallSetElementSetNames
208                 = (Z_ElementSetNames *)
209                 odr_malloc(odr_en, sizeof(Z_ElementSetNames));
210             sr_req->smallSetElementSetNames->which = Z_ElementSetNames_generic;
211             sr_req->smallSetElementSetNames->u.generic
212                 = odr_strdup(odr_en, backend_schema);
213             sr_req->mediumSetElementSetNames = sr_req->smallSetElementSetNames;
214         }
215         else
216         {
217             sr_req->smallSetElementSetNames = 0;
218             sr_req->mediumSetElementSetNames = 0;
219         }
220     }
221     else if (pr_req)
222     {
223         if (backend_syntax)
224             pr_req->preferredRecordSyntax = odr_oiddup(odr_en, backend_syntax);
225         else
226             pr_req->preferredRecordSyntax = 0;
227
228         if (backend_schema)
229         {
230             pr_req->recordComposition
231                 = (Z_RecordComposition *)
232                 odr_malloc(odr_en, sizeof(Z_RecordComposition));
233             pr_req->recordComposition->which
234                 = Z_RecordComp_simple;
235             pr_req->recordComposition->u.simple
236                 = (Z_ElementSetNames *)
237                 odr_malloc(odr_en, sizeof(Z_ElementSetNames));
238             pr_req->recordComposition->u.simple->which = Z_ElementSetNames_generic;
239             pr_req->recordComposition->u.simple->u.generic
240                 = odr_strdup(odr_en, backend_schema);
241         }
242         else
243             pr_req->recordComposition = 0;
244     }
245     package.move();
246
247     Z_GDU *gdu_res = package.response().get();
248
249     // see if we have a records list to patch!
250     Z_NamePlusRecordList *records = 0;
251     if (gdu_res && gdu_res->which == Z_GDU_Z3950 &&
252         gdu_res->u.z3950->which == Z_APDU_presentResponse)
253     {
254         Z_PresentResponse * pr_res = gdu_res->u.z3950->u.presentResponse;
255
256         if (pr_res
257             && pr_res->numberOfRecordsReturned
258             && *(pr_res->numberOfRecordsReturned) > 0
259             && pr_res->records
260             && pr_res->records->which == Z_Records_DBOSD)
261         {
262             records = pr_res->records->u.databaseOrSurDiagnostics;
263         }
264     }
265     if (gdu_res && gdu_res->which == Z_GDU_Z3950 &&
266         gdu_res->u.z3950->which == Z_APDU_searchResponse)
267     {
268         Z_SearchResponse *sr_res = gdu_res->u.z3950->u.searchResponse;
269
270         if (sr_res
271             && sr_res->numberOfRecordsReturned
272             && *(sr_res->numberOfRecordsReturned) > 0
273             && sr_res->records
274             && sr_res->records->which == Z_Records_DBOSD)
275         {
276             records = sr_res->records->u.databaseOrSurDiagnostics;
277         }
278     }
279     if (records)
280     {
281         int i;
282         for (i = 0; i < records->num_records; i++)
283         {
284             Z_NamePlusRecord **npr = &records->records[i];
285             if ((*npr)->which == Z_NamePlusRecord_databaseRecord)
286             {
287                 const char *details = 0;
288                 Z_External *r = (*npr)->u.databaseRecord;
289                 int ret_trans = -1;
290                 if (r->which == Z_External_octet &&
291                     !oid_oidcmp(r->direct_reference, yaz_oid_recsyn_xml))
292                 {
293                     std::string result;
294                     if (convert_one_record(
295                         r->u.octet_aligned->buf, r->u.octet_aligned->len,
296                         result))
297                     {
298                         (*npr)->u.databaseRecord =
299                             z_ext_record_oid(odr_en, yaz_oid_recsyn_xml,
300                                              result.c_str(),
301                                              result.length());
302                     }
303                     else
304                     {
305                         *npr = zget_surrogateDiagRec(
306                             odr_en, (*npr)->databaseName,
307                             YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS,
308                             result.c_str());
309                     }
310                 }
311             }
312         }
313         package.response() = gdu_res;
314     }
315 }
316
317 void yf::XQuery::configure(const xmlNode * ptr, bool test_only,
318                            const char *path)
319 {
320     for (ptr = ptr->children; ptr; ptr = ptr->next)
321     {
322         if (ptr->type != XML_ELEMENT_NODE)
323             continue;
324         if (!strcmp((const char *) ptr->name, "elementset"))
325         {
326             struct _xmlAttr *attr;
327             for (attr = ptr->properties; attr; attr = attr->next)
328                 if (!strcmp((const char *) attr->name, "name"))
329                     elementset_input = mp::xml::get_text(attr->children);
330                 else if (!strcmp((const char *) attr->name, "backend"))
331                     elementset_output = mp::xml::get_text(attr->children);
332                 else
333                     throw mp::filter::FilterException(
334                         "Bad attribute " + std::string((const char *)
335                                                        attr->name));
336         }
337         else if (!strcmp((const char *) ptr->name, "variable"))
338         {
339             std::string name;
340             std::string value;
341             struct _xmlAttr *attr;
342             for (attr = ptr->properties; attr; attr = attr->next)
343                 if (!strcmp((const char *) attr->name, "name"))
344                     name = mp::xml::get_text(attr->children);
345                 else if (!strcmp((const char *) attr->name, "value"))
346                     value = mp::xml::get_text(attr->children);
347                 else
348                     throw mp::filter::FilterException(
349                         "Bad attribute " + std::string((const char *)
350                                                        attr->name));
351             if (name.length() > 0)
352                 zorba_variables[name] = value;
353         }
354         else if (!strcmp((const char *) ptr->name, "script"))
355         {
356             std::string name;
357             struct _xmlAttr *attr;
358             for (attr = ptr->properties; attr; attr = attr->next)
359                 if (!strcmp((const char *) attr->name, "name"))
360                     name = mp::xml::get_text(attr->children);
361                 else
362                     throw mp::filter::FilterException(
363                         "Bad attribute " + std::string((const char *)
364                                                        attr->name));
365             zorba_script = name;
366         }
367         else if (!strcmp((const char *) ptr->name, "record"))
368         {
369             std::string name;
370             struct _xmlAttr *attr;
371             for (attr = ptr->properties; attr; attr = attr->next)
372                 if (!strcmp((const char *) attr->name, "name"))
373                     name = mp::xml::get_text(attr->children);
374                 else
375                     throw mp::filter::FilterException(
376                         "Bad attribute " + std::string((const char *)
377                                                        attr->name));
378             zorba_record_variable = name;
379         }
380         else
381         {
382             throw mp::filter::FilterException("Bad element "
383                                                + std::string((const char *)
384                                                              ptr->name));
385         }
386     }
387     if (zorba_script.length() == 0)
388         throw mp::filter::FilterException("Missing element script");
389     if (zorba_record_variable.length() == 0)
390         throw mp::filter::FilterException("Missing element record");
391     if (!test_only)
392     {
393         void* lStore = StoreManager::getStore();
394         lZorba = Zorba::getInstance(lStore);
395
396         lQuery = lZorba->createQuery();
397
398         try {
399             size_t t = zorba_script.find_last_of('/');
400             if (t != std::string::npos)
401                 lQuery->setFileName(zorba_script.substr(0, t + 1));
402             std::unique_ptr<std::istream> qfile(
403                 new std::ifstream(zorba_script.c_str()));
404             Zorba_CompilerHints lHints;
405             lQuery->compile(*qfile, lHints);
406         } catch ( ZorbaException &e) {
407             std::string msg = "XQuery compile: ";
408             msg += e.what();
409             throw mp::filter::FilterException(msg);
410         }
411     }
412 }
413
414 static yf::Base* filter_creator()
415 {
416     return new mp::filter::XQuery;
417 }
418
419 extern "C" {
420     struct metaproxy_1_filter_struct metaproxy_1_filter_xquery = {
421         0,
422         "xquery",
423         filter_creator
424     };
425 }
426
427
428 /*
429  * Local variables:
430  * c-basic-offset: 4
431  * c-file-style: "Stroustrup"
432  * indent-tabs-mode: nil
433  * End:
434  * vim: shiftwidth=4 tabstop=8 expandtab
435  */
436