Fix Metaproxy stops logging after check config failed MP-590
[metaproxy-moved-to-github.git] / src / filter_record_transform.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_record_transform.hpp"
21 #include <metaproxy/package.hpp>
22 #include <metaproxy/util.hpp>
23 #include "gduutil.hpp"
24
25 #include <yaz/diagbib1.h>
26 #include <yaz/zgdu.h>
27 #include <yaz/retrieval.h>
28 #include <yaz/oid_db.h>
29
30 #include <boost/thread/mutex.hpp>
31
32 #if HAVE_USEMARCON
33 #include <usemarconlib.h>
34 #include <defines.h>
35 #endif
36
37 #include <iostream>
38
39 namespace mp = metaproxy_1;
40 namespace yf = mp::filter;
41 namespace mp_util = metaproxy_1::util;
42
43 namespace metaproxy_1 {
44     namespace filter {
45         class RecordTransform::Impl {
46         public:
47             Impl();
48             ~Impl();
49             void process(metaproxy_1::Package & package) const;
50             void configure(const xmlNode * xml_node, const char *path);
51         private:
52             yaz_retrieval_t m_retrieval;
53         };
54     }
55 }
56
57 #if HAVE_USEMARCON
58 struct info_usemarcon {
59     boost::mutex m_mutex;
60
61     char *stage1;
62     char *stage2;
63
64     Usemarcon *usemarcon1;
65     Usemarcon *usemarcon2;
66 };
67
68 static int convert_usemarcon(void *info, WRBUF record, WRBUF wr_error)
69 {
70     struct info_usemarcon *p = (struct info_usemarcon *) info;
71
72     boost::mutex::scoped_lock lock(p->m_mutex);
73
74     if (p->usemarcon1)
75     {
76         char *converted;
77         size_t convlen;
78         int res;
79
80         p->usemarcon1->SetMarcRecord(wrbuf_buf(record), wrbuf_len(record));
81         res = p->usemarcon1->Convert();
82         if (res != 0)
83         {
84             wrbuf_printf(wr_error, "usemarcon stage1 failed res=%d", res);
85             return -1;
86         }
87         p->usemarcon1->GetMarcRecord(converted, convlen);
88
89         if (p->usemarcon2)
90         {
91             p->usemarcon2->SetMarcRecord(converted, convlen);
92
93             res = p->usemarcon2->Convert();
94             free(converted);
95             if (res != 0)
96             {
97                 wrbuf_printf(wr_error, "usemarcon stage2 failed res=%d",
98                              res);
99                 return -1;
100             }
101             p->usemarcon2->GetMarcRecord(converted, convlen);
102         }
103         wrbuf_rewind(record);
104         wrbuf_write(record, converted, convlen);
105         free(converted);
106     }
107     return 0;
108 }
109
110 static void destroy_usemarcon(void *info)
111 {
112     struct info_usemarcon *p = (struct info_usemarcon *) info;
113
114     delete p->usemarcon1;
115     delete p->usemarcon2;
116     xfree(p->stage1);
117     xfree(p->stage2);
118     delete p;
119 }
120
121 static void *construct_usemarcon(const xmlNode *ptr, const char *path,
122                                  WRBUF wr_error)
123 {
124     struct _xmlAttr *attr;
125     if (strcmp((const char *) ptr->name, "usemarcon"))
126         return 0;
127
128     struct info_usemarcon *p = new(struct info_usemarcon);
129     p->stage1 = 0;
130     p->stage2 = 0;
131     p->usemarcon1 = 0;
132     p->usemarcon2 = 0;
133
134     for (attr = ptr->properties; attr; attr = attr->next)
135     {
136         if (!xmlStrcmp(attr->name, BAD_CAST "stage1") &&
137             attr->children && attr->children->type == XML_TEXT_NODE)
138             p->stage1 = xstrdup((const char *) attr->children->content);
139         else if (!xmlStrcmp(attr->name, BAD_CAST "stage2") &&
140             attr->children && attr->children->type == XML_TEXT_NODE)
141             p->stage2 = xstrdup((const char *) attr->children->content);
142         else
143         {
144             wrbuf_printf(wr_error, "Bad attribute '%s'"
145                          "Expected stage1 or stage2.", attr->name);
146             destroy_usemarcon(p);
147             return 0;
148         }
149     }
150
151     if (p->stage1)
152     {
153         p->usemarcon1 = new Usemarcon();
154         p->usemarcon1->SetIniFileName(p->stage1);
155     }
156     if (p->stage2)
157     {
158         p->usemarcon2 = new Usemarcon();
159         p->usemarcon2->SetIniFileName(p->stage2);
160     }
161     return p;
162 }
163
164 static void type_usemarcon(struct yaz_record_conv_type *t)
165 {
166     t->next = 0;
167     t->construct = construct_usemarcon;
168     t->convert = convert_usemarcon;
169     t->destroy = destroy_usemarcon;
170 }
171 #endif
172
173 // define Pimpl wrapper forwarding to Impl
174
175 yf::RecordTransform::RecordTransform() : m_p(new Impl)
176 {
177 }
178
179 yf::RecordTransform::~RecordTransform()
180 {  // must have a destructor because of boost::scoped_ptr
181 }
182
183 void yf::RecordTransform::configure(const xmlNode *xmlnode, bool test_only,
184                                     const char *path)
185 {
186     m_p->configure(xmlnode, path);
187 }
188
189 void yf::RecordTransform::process(mp::Package &package) const
190 {
191     m_p->process(package);
192 }
193
194
195 yf::RecordTransform::Impl::Impl()
196 {
197     m_retrieval = yaz_retrieval_create();
198     assert(m_retrieval);
199 }
200
201 yf::RecordTransform::Impl::~Impl()
202 {
203     if (m_retrieval)
204         yaz_retrieval_destroy(m_retrieval);
205 }
206
207 void yf::RecordTransform::Impl::configure(const xmlNode *xml_node,
208                                           const char *path)
209 {
210     yaz_retrieval_set_path(m_retrieval, path);
211
212     if (!xml_node)
213         throw mp::XMLError("RecordTransform filter config: empty XML DOM");
214
215     // parsing down to retrieval node, which can be any of the children nodes
216     xmlNode *retrieval_node;
217     for (retrieval_node = xml_node->children;
218          retrieval_node;
219          retrieval_node = retrieval_node->next)
220     {
221         if (retrieval_node->type != XML_ELEMENT_NODE)
222             continue;
223         if (0 == strcmp((const char *) retrieval_node->name, "retrievalinfo"))
224             break;
225     }
226
227 #if HAVE_USEMARCON
228     struct yaz_record_conv_type mt;
229     type_usemarcon(&mt);
230     struct yaz_record_conv_type *t = &mt;
231 #else
232     struct yaz_record_conv_type *t = 0;
233 #endif
234
235     // read configuration
236     if (0 != yaz_retrieval_configure_t(m_retrieval, retrieval_node, t))
237     {
238         std::string msg("RecordTransform filter config: ");
239         msg += yaz_retrieval_get_error(m_retrieval);
240         throw mp::XMLError(msg);
241     }
242 }
243
244 void yf::RecordTransform::Impl::process(mp::Package &package) const
245 {
246
247     Z_GDU *gdu_req = package.request().get();
248     Z_PresentRequest *pr_req = 0;
249     Z_SearchRequest *sr_req = 0;
250
251     const char *input_schema = 0;
252     Odr_oid *input_syntax = 0;
253
254     if (gdu_req && gdu_req->which == Z_GDU_Z3950 &&
255         gdu_req->u.z3950->which == Z_APDU_presentRequest)
256     {
257         pr_req = gdu_req->u.z3950->u.presentRequest;
258
259         input_schema =
260             mp_util::record_composition_to_esn(pr_req->recordComposition);
261         input_syntax = pr_req->preferredRecordSyntax;
262     }
263     else if (gdu_req && gdu_req->which == Z_GDU_Z3950 &&
264              gdu_req->u.z3950->which == Z_APDU_searchRequest)
265     {
266         sr_req = gdu_req->u.z3950->u.searchRequest;
267
268         input_syntax = sr_req->preferredRecordSyntax;
269
270         // we don't know how many hits we're going to get and therefore
271         // the effective element set name.. Therefore we can only allow
272         // two cases.. Both equal or absent.. If not, we'll just have to
273         // disable the piggyback!
274         if (sr_req->smallSetElementSetNames
275             &&
276             sr_req->mediumSetElementSetNames
277             &&
278             sr_req->smallSetElementSetNames->which == Z_ElementSetNames_generic
279             &&
280             sr_req->mediumSetElementSetNames->which == Z_ElementSetNames_generic
281             &&
282             !strcmp(sr_req->smallSetElementSetNames->u.generic,
283                     sr_req->mediumSetElementSetNames->u.generic))
284         {
285             input_schema = sr_req->smallSetElementSetNames->u.generic;
286         }
287         else if (!sr_req->smallSetElementSetNames &&
288                  !sr_req->mediumSetElementSetNames)
289             ; // input_schema is 0 already
290         else
291         {
292             // disable piggyback (perhaps it was disabled already)
293             *sr_req->smallSetUpperBound = 0;
294             *sr_req->largeSetLowerBound = 0;
295             *sr_req->mediumSetPresentNumber = 0;
296             package.move();
297             return;
298         }
299         // we can handle it in record_transform.
300     }
301     else
302     {
303         package.move();
304         return;
305     }
306
307     mp::odr odr_en(ODR_ENCODE);
308
309     // setting up variables for conversion state
310     yaz_record_conv_t rc = 0;
311
312     const char *match_schema = 0;
313     Odr_oid *match_syntax = 0;
314
315     const char *backend_schema = 0;
316     Odr_oid *backend_syntax = 0;
317
318     int ret_code
319         = yaz_retrieval_request(m_retrieval,
320                                 input_schema, input_syntax,
321                                 &match_schema, &match_syntax,
322                                 &rc,
323                                 &backend_schema, &backend_syntax);
324     // error handling
325     if (ret_code != 0)
326     {
327         int error_code;
328         const char *details = 0;
329
330         if (ret_code == -1) /* error ? */
331         {
332             details = yaz_retrieval_get_error(m_retrieval);
333             error_code = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
334         }
335         else if (ret_code == 1 || ret_code == 3)
336         {
337             details = input_schema;
338             error_code = YAZ_BIB1_ELEMENT_SET_NAMES_UNSUPP;
339         }
340         else if (ret_code == 2)
341         {
342             char oidbuf[OID_STR_MAX];
343             oid_oid_to_dotstring(input_syntax, oidbuf);
344             details = odr_strdup(odr_en, oidbuf);
345             error_code = YAZ_BIB1_RECORD_SYNTAX_UNSUPP;
346         }
347         else
348         {
349             char *tmp = (char*) odr_malloc(odr_en, 80);
350             sprintf(tmp,
351                     "record_transform: yaz_retrieval_get_error returned %d",
352                     ret_code);
353             details = tmp;
354             error_code = YAZ_BIB1_UNSPECIFIED_ERROR;
355         }
356         Z_APDU *apdu;
357         if (sr_req)
358         {
359             apdu = odr_en.create_searchResponse(
360                 gdu_req->u.z3950, error_code, details);
361         }
362         else
363         {
364             apdu = odr_en.create_presentResponse(
365                 gdu_req->u.z3950, error_code, details);
366         }
367         package.response() = apdu;
368         return;
369     }
370
371     if (sr_req)
372     {
373         if (backend_syntax)
374             sr_req->preferredRecordSyntax = odr_oiddup(odr_en, backend_syntax);
375         else
376             sr_req->preferredRecordSyntax = 0;
377
378         if (backend_schema)
379         {
380             sr_req->smallSetElementSetNames
381                 = (Z_ElementSetNames *)
382                 odr_malloc(odr_en, sizeof(Z_ElementSetNames));
383             sr_req->smallSetElementSetNames->which = Z_ElementSetNames_generic;
384             sr_req->smallSetElementSetNames->u.generic
385                 = odr_strdup(odr_en, backend_schema);
386             sr_req->mediumSetElementSetNames = sr_req->smallSetElementSetNames;
387         }
388         else
389         {
390             sr_req->smallSetElementSetNames = 0;
391             sr_req->mediumSetElementSetNames = 0;
392         }
393     }
394     else if (pr_req)
395     {
396         if (backend_syntax)
397             pr_req->preferredRecordSyntax = odr_oiddup(odr_en, backend_syntax);
398         else
399             pr_req->preferredRecordSyntax = 0;
400
401         if (backend_schema)
402         {
403             pr_req->recordComposition
404                 = (Z_RecordComposition *)
405                 odr_malloc(odr_en, sizeof(Z_RecordComposition));
406             pr_req->recordComposition->which
407                 = Z_RecordComp_simple;
408             pr_req->recordComposition->u.simple
409                 = (Z_ElementSetNames *)
410                 odr_malloc(odr_en, sizeof(Z_ElementSetNames));
411             pr_req->recordComposition->u.simple->which = Z_ElementSetNames_generic;
412             pr_req->recordComposition->u.simple->u.generic
413                 = odr_strdup(odr_en, backend_schema);
414         }
415         else
416             pr_req->recordComposition = 0;
417     }
418
419     // attaching Z3950 package to filter chain
420     package.request() = gdu_req;
421
422     package.move();
423
424     Z_GDU *gdu_res = package.response().get();
425
426     // see if we have a records list to patch!
427     Z_NamePlusRecordList *records = 0;
428     if (gdu_res && gdu_res->which == Z_GDU_Z3950 &&
429         gdu_res->u.z3950->which == Z_APDU_presentResponse)
430     {
431         Z_PresentResponse * pr_res = gdu_res->u.z3950->u.presentResponse;
432
433         if (rc && pr_res
434             && pr_res->numberOfRecordsReturned
435             && *(pr_res->numberOfRecordsReturned) > 0
436             && pr_res->records
437             && pr_res->records->which == Z_Records_DBOSD)
438         {
439             records = pr_res->records->u.databaseOrSurDiagnostics;
440         }
441     }
442     if (gdu_res && gdu_res->which == Z_GDU_Z3950 &&
443         gdu_res->u.z3950->which == Z_APDU_searchResponse)
444     {
445         Z_SearchResponse *sr_res = gdu_res->u.z3950->u.searchResponse;
446
447         if (rc && sr_res
448             && sr_res->numberOfRecordsReturned
449             && *(sr_res->numberOfRecordsReturned) > 0
450             && sr_res->records
451             && sr_res->records->which == Z_Records_DBOSD)
452         {
453             records = sr_res->records->u.databaseOrSurDiagnostics;
454         }
455     }
456
457     if (records)
458     {
459         int i;
460         for (i = 0; i < records->num_records; i++)
461         {
462             Z_NamePlusRecord *npr = records->records[i];
463             if (npr->which == Z_NamePlusRecord_databaseRecord)
464             {
465                 const char *details = 0;
466                 mp::wrbuf output_record;
467                 Z_External *r = npr->u.databaseRecord;
468                 int ret_trans = -1;
469                 if (r->which == Z_External_OPAC)
470                 {
471                     ret_trans =
472                         yaz_record_conv_opac_record(rc, r->u.opac,
473                                                     output_record);
474                     details = yaz_record_conv_get_error(rc);
475                 }
476                 else if (r->which == Z_External_octet)
477                 {
478                     ret_trans =
479                         yaz_record_conv_record(rc, (const char *)
480                                                r->u.octet_aligned->buf,
481                                                r->u.octet_aligned->len,
482                                                output_record);
483                     details = yaz_record_conv_get_error(rc);
484                 }
485                 else
486                 {
487                     details = "unsupported record type for record_conv";
488                 }
489                 if (ret_trans)
490                 {
491                     records->records[i] =
492                         zget_surrogateDiagRec(
493                             odr_en, npr->databaseName,
494                             YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS,
495                             details);
496                 }
497                 else
498                 {
499                     if (!oid_oidcmp(match_syntax, yaz_oid_recsyn_opac))
500                     {
501                         yaz_marc_t mt = yaz_marc_create();
502                         Z_OPACRecord *opac = 0;
503                         if (yaz_xml_to_opac(mt, output_record.buf(),
504                                             output_record.len(),
505                                             &opac, 0 /* iconv */,
506                                             ((ODR )odr_en)->mem, 0)
507                             && opac)
508                         {
509                             npr->u.databaseRecord =
510                                 z_ext_record_oid(odr_en, match_syntax,
511                                                  (const char *) opac, -1);
512                         }
513                         else
514                         {
515                             records->records[i] =
516                                 zget_surrogateDiagRec(
517                                     odr_en, npr->databaseName,
518                                     YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS,
519                                     "XML to OPAC conversion failed");
520                         }
521                         yaz_marc_destroy(mt);
522                     }
523                     else
524                     {
525                         npr->u.databaseRecord =
526                             z_ext_record_oid(odr_en, match_syntax,
527                                              output_record.buf(),
528                                              output_record.len());
529                     }
530                 }
531             }
532         }
533         package.response() = gdu_res;
534     }
535     return;
536 }
537
538 static mp::filter::Base* filter_creator()
539 {
540     return new mp::filter::RecordTransform;
541 }
542
543 extern "C" {
544     struct metaproxy_1_filter_struct metaproxy_1_filter_record_transform = {
545         0,
546         "record_transform",
547         filter_creator
548     };
549 }
550
551
552 /*
553  * Local variables:
554  * c-basic-offset: 4
555  * c-file-style: "Stroustrup"
556  * indent-tabs-mode: nil
557  * End:
558  * vim: shiftwidth=4 tabstop=8 expandtab
559  */
560