metaproxy  1.21.0
filter_record_transform.cpp
Go to the documentation of this file.
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"
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 {
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 static void convert_npr(yaz_record_conv_t rc,
245  Odr_oid *match_syntax,
246  ODR odr_en,
247  Z_NamePlusRecordList *records, int i)
248 {
249  Z_NamePlusRecord *npr = records->records[i];
250  const char *details = 0;
251  mp::wrbuf output_record;
252  Z_External *r = npr->u.databaseRecord;
253  int ret_trans = -1;
254  if (npr->which != Z_NamePlusRecord_databaseRecord)
255  return;
256  r = npr->u.databaseRecord;
257  if (r->which == Z_External_OPAC)
258  {
259  ret_trans = yaz_record_conv_opac_record(rc, r->u.opac,
260  output_record);
261  details = yaz_record_conv_get_error(rc);
262  }
263  else if (r->which == Z_External_octet)
264  {
265  ret_trans =
266  yaz_record_conv_record(
267  rc, (const char *) r->u.octet_aligned->buf,
268  r->u.octet_aligned->len, output_record);
269  details = yaz_record_conv_get_error(rc);
270  }
271  else
272  {
273  details = "unsupported record type for record_conv";
274  }
275  if (ret_trans)
276  {
277  records->records[i] =
278  zget_surrogateDiagRec(
279  odr_en, npr->databaseName,
280  YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS,
281  details);
282  }
283  else
284  {
285  if (!oid_oidcmp(match_syntax, yaz_oid_recsyn_opac))
286  {
287  yaz_iconv_t cd = 0;
288  yaz_marc_t mt = yaz_marc_create();
289  Z_OPACRecord *opac = 0;
290  const char *output_charset =
291  yaz_record_get_output_charset(rc);
292  if (output_charset)
293  cd = yaz_iconv_open(output_charset, "utf-8");
294  if (yaz_xml_to_opac(mt, output_record.buf(),
295  output_record.len(),
296  &opac, cd,
297  ((ODR )odr_en)->mem, 0)
298  && opac)
299  {
300  npr->u.databaseRecord =
301  z_ext_record_oid(odr_en, match_syntax,
302  (const char *) opac, -1);
303  }
304  else
305  {
306  records->records[i] =
307  zget_surrogateDiagRec(
308  odr_en, npr->databaseName,
309  YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS,
310  "XML to OPAC conversion failed");
311  }
312  yaz_marc_destroy(mt);
313  }
314  else
315  {
316  npr->u.databaseRecord = z_ext_record_oid(odr_en, match_syntax,
317  output_record.buf(),
318  output_record.len());
319  }
320  }
321 }
322 
323 void yf::RecordTransform::Impl::process(mp::Package &package) const
324 {
325 
326  Z_GDU *gdu_req = package.request().get();
327  Z_PresentRequest *pr_req = 0;
328  Z_SearchRequest *sr_req = 0;
329 
330  const char *input_schema = 0;
331  Odr_oid *input_syntax = 0;
332 
333  if (gdu_req && gdu_req->which == Z_GDU_Z3950 &&
334  gdu_req->u.z3950->which == Z_APDU_presentRequest)
335  {
336  pr_req = gdu_req->u.z3950->u.presentRequest;
337 
338  input_schema =
339  mp_util::record_composition_to_esn(pr_req->recordComposition);
340  input_syntax = pr_req->preferredRecordSyntax;
341  }
342  else if (gdu_req && gdu_req->which == Z_GDU_Z3950 &&
343  gdu_req->u.z3950->which == Z_APDU_searchRequest)
344  {
345  sr_req = gdu_req->u.z3950->u.searchRequest;
346 
347  input_syntax = sr_req->preferredRecordSyntax;
348 
349  // we don't know how many hits we're going to get and therefore
350  // the effective element set name.. Therefore we can only allow
351  // two cases.. Both equal or absent.. If not, we'll just have to
352  // disable the piggyback!
353  if (sr_req->smallSetElementSetNames
354  &&
355  sr_req->mediumSetElementSetNames
356  &&
357  sr_req->smallSetElementSetNames->which == Z_ElementSetNames_generic
358  &&
359  sr_req->mediumSetElementSetNames->which == Z_ElementSetNames_generic
360  &&
361  !strcmp(sr_req->smallSetElementSetNames->u.generic,
362  sr_req->mediumSetElementSetNames->u.generic))
363  {
364  input_schema = sr_req->smallSetElementSetNames->u.generic;
365  }
366  else if (*sr_req->largeSetLowerBound > 1
367  && !sr_req->smallSetElementSetNames && !sr_req->mediumSetElementSetNames)
368  ; // input_schema is 0 already
369  else
370  {
371  // disable piggyback (perhaps it was disabled already)
372  *sr_req->smallSetUpperBound = 0;
373  *sr_req->largeSetLowerBound = 1;
374  *sr_req->mediumSetPresentNumber = 0;
375  package.move();
376  return;
377  }
378  // we can handle it in record_transform.
379  }
380  else
381  {
382  package.move();
383  return;
384  }
385 
386  mp::odr odr_en(ODR_ENCODE);
387 
388  // setting up variables for conversion state
389  yaz_record_conv_t rc = 0;
390 
391  const char *match_schema = 0;
392  Odr_oid *match_syntax = 0;
393 
394  const char *backend_schema = 0;
395  Odr_oid *backend_syntax = 0;
396 
397  int ret_code
398  = yaz_retrieval_request(m_retrieval,
399  input_schema, input_syntax,
400  &match_schema, &match_syntax,
401  &rc,
402  &backend_schema, &backend_syntax);
403  // error handling
404  if (ret_code != 0)
405  {
406  int error_code;
407  const char *details = 0;
408 
409  if (ret_code == -1) /* error ? */
410  {
411  details = yaz_retrieval_get_error(m_retrieval);
412  error_code = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
413  }
414  else if (ret_code == 1 || ret_code == 3)
415  {
416  details = input_schema;
417  error_code = YAZ_BIB1_ELEMENT_SET_NAMES_UNSUPP;
418  }
419  else if (ret_code == 2)
420  {
421  char oidbuf[OID_STR_MAX];
422  oid_oid_to_dotstring(input_syntax, oidbuf);
423  details = odr_strdup(odr_en, oidbuf);
424  error_code = YAZ_BIB1_RECORD_SYNTAX_UNSUPP;
425  }
426  else
427  {
428  char *tmp = (char*) odr_malloc(odr_en, 80);
429  sprintf(tmp,
430  "record_transform: yaz_retrieval_get_error returned %d",
431  ret_code);
432  details = tmp;
433  error_code = YAZ_BIB1_UNSPECIFIED_ERROR;
434  }
435  Z_APDU *apdu;
436  if (sr_req)
437  {
438  apdu = odr_en.create_searchResponse(
439  gdu_req->u.z3950, error_code, details);
440  }
441  else
442  {
443  apdu = odr_en.create_presentResponse(
444  gdu_req->u.z3950, error_code, details);
445  }
446  package.response() = apdu;
447  return;
448  }
449 
450  if (sr_req)
451  {
452  if (backend_syntax)
453  sr_req->preferredRecordSyntax = odr_oiddup(odr_en, backend_syntax);
454  else
455  sr_req->preferredRecordSyntax = 0;
456 
457  if (backend_schema)
458  {
459  sr_req->smallSetElementSetNames
460  = (Z_ElementSetNames *)
461  odr_malloc(odr_en, sizeof(Z_ElementSetNames));
462  sr_req->smallSetElementSetNames->which = Z_ElementSetNames_generic;
463  sr_req->smallSetElementSetNames->u.generic
464  = odr_strdup(odr_en, backend_schema);
465  sr_req->mediumSetElementSetNames = sr_req->smallSetElementSetNames;
466  }
467  else
468  {
469  sr_req->smallSetElementSetNames = 0;
470  sr_req->mediumSetElementSetNames = 0;
471  }
472  }
473  else if (pr_req)
474  {
475  if (backend_syntax)
476  pr_req->preferredRecordSyntax = odr_oiddup(odr_en, backend_syntax);
477  else
478  pr_req->preferredRecordSyntax = 0;
479 
480  if (backend_schema)
481  {
482  pr_req->recordComposition
483  = (Z_RecordComposition *)
484  odr_malloc(odr_en, sizeof(Z_RecordComposition));
485  pr_req->recordComposition->which
486  = Z_RecordComp_simple;
487  pr_req->recordComposition->u.simple
488  = (Z_ElementSetNames *)
489  odr_malloc(odr_en, sizeof(Z_ElementSetNames));
490  pr_req->recordComposition->u.simple->which = Z_ElementSetNames_generic;
491  pr_req->recordComposition->u.simple->u.generic
492  = odr_strdup(odr_en, backend_schema);
493  }
494  else
495  pr_req->recordComposition = 0;
496  }
497 
498  // attaching Z3950 package to filter chain
499  package.request() = gdu_req;
500 
501  package.move();
502 
503  Z_GDU *gdu_res = package.response().get();
504 
505  // see if we have a records list to patch!
506  Z_NamePlusRecordList *records = 0;
507  if (gdu_res && gdu_res->which == Z_GDU_Z3950 &&
508  gdu_res->u.z3950->which == Z_APDU_presentResponse)
509  {
510  Z_PresentResponse * pr_res = gdu_res->u.z3950->u.presentResponse;
511 
512  if (rc && pr_res
513  && pr_res->numberOfRecordsReturned
514  && *(pr_res->numberOfRecordsReturned) > 0
515  && pr_res->records
516  && pr_res->records->which == Z_Records_DBOSD)
517  {
518  records = pr_res->records->u.databaseOrSurDiagnostics;
519  }
520  }
521  if (gdu_res && gdu_res->which == Z_GDU_Z3950 &&
522  gdu_res->u.z3950->which == Z_APDU_searchResponse)
523  {
524  Z_SearchResponse *sr_res = gdu_res->u.z3950->u.searchResponse;
525 
526  if (rc && sr_res
527  && sr_res->numberOfRecordsReturned
528  && *(sr_res->numberOfRecordsReturned) > 0
529  && sr_res->records
530  && sr_res->records->which == Z_Records_DBOSD)
531  {
532  records = sr_res->records->u.databaseOrSurDiagnostics;
533  }
534  }
535 
536  if (records)
537  {
538  int i;
539  for (i = 0; i < records->num_records; i++)
540  convert_npr(rc, match_syntax, odr_en, records, i);
541  package.response() = gdu_res;
542  }
543  return;
544 }
545 
546 static mp::filter::Base* filter_creator()
547 {
548  return new mp::filter::RecordTransform;
549 }
550 
551 extern "C" {
552  struct metaproxy_1_filter_struct metaproxy_1_filter_record_transform = {
553  0,
554  "record_transform",
556  };
557 }
558 
559 
560 /*
561  * Local variables:
562  * c-basic-offset: 4
563  * c-file-style: "Stroustrup"
564  * indent-tabs-mode: nil
565  * End:
566  * vim: shiftwidth=4 tabstop=8 expandtab
567  */
568 
void process(metaproxy_1::Package &package) const
void configure(const xmlNode *xml_node, const char *path)
static void convert_npr(yaz_record_conv_t rc, Odr_oid *match_syntax, ODR odr_en, Z_NamePlusRecordList *records, int i)
static mp::filter::Base * filter_creator()
struct metaproxy_1_filter_struct metaproxy_1_filter_record_transform