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