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