record_transform: honor piggyback searches
[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 <iostream>
30
31 namespace mp = metaproxy_1;
32 namespace yf = mp::filter;
33 namespace mp_util = metaproxy_1::util;
34
35 namespace metaproxy_1 {
36     namespace filter {
37         class RecordTransform::Impl {
38         public:
39             Impl();
40             ~Impl();
41             void process(metaproxy_1::Package & package) const;
42             void configure(const xmlNode * xml_node, const char *path);
43         private:
44             yaz_retrieval_t m_retrieval;
45         };
46     }
47 }
48
49 // define Pimpl wrapper forwarding to Impl
50  
51 yf::RecordTransform::RecordTransform() : m_p(new Impl)
52 {
53 }
54
55 yf::RecordTransform::~RecordTransform()
56 {  // must have a destructor because of boost::scoped_ptr
57 }
58
59 void yf::RecordTransform::configure(const xmlNode *xmlnode, bool test_only,
60                                     const char *path)
61 {
62     m_p->configure(xmlnode, path);
63 }
64
65 void yf::RecordTransform::process(mp::Package &package) const
66 {
67     m_p->process(package);
68 }
69
70
71 yf::RecordTransform::Impl::Impl() 
72 {
73     m_retrieval = yaz_retrieval_create();
74     assert(m_retrieval);
75 }
76
77 yf::RecordTransform::Impl::~Impl()
78
79     if (m_retrieval)
80         yaz_retrieval_destroy(m_retrieval);
81 }
82
83 void yf::RecordTransform::Impl::configure(const xmlNode *xml_node,
84                                           const char *path)
85 {
86     yaz_retrieval_set_path(m_retrieval, path);
87
88     if (!xml_node)
89         throw mp::XMLError("RecordTransform filter config: empty XML DOM");
90
91     // parsing down to retrieval node, which can be any of the children nodes
92     xmlNode *retrieval_node;
93     for (retrieval_node = xml_node->children; 
94          retrieval_node; 
95          retrieval_node = retrieval_node->next)
96     {
97         if (retrieval_node->type != XML_ELEMENT_NODE)
98             continue;
99         if (0 == strcmp((const char *) retrieval_node->name, "retrievalinfo"))
100             break;
101     }
102
103     // read configuration
104     if (0 != yaz_retrieval_configure(m_retrieval, retrieval_node))
105     {
106         std::string msg("RecordTransform filter config: ");
107         msg += yaz_retrieval_get_error(m_retrieval);
108         throw mp::XMLError(msg);
109     }
110 }
111
112 void yf::RecordTransform::Impl::process(mp::Package &package) const
113 {
114
115     Z_GDU *gdu_req = package.request().get();
116     Z_PresentRequest *pr_req = 0;
117     Z_SearchRequest *sr_req = 0;
118
119     const char *input_schema = 0;
120     Odr_oid *input_syntax = 0;
121
122     if (gdu_req && gdu_req->which == Z_GDU_Z3950 &&
123         gdu_req->u.z3950->which == Z_APDU_presentRequest)
124     {
125         pr_req = gdu_req->u.z3950->u.presentRequest;
126
127         input_schema =
128             mp_util::record_composition_to_esn(pr_req->recordComposition);
129         input_syntax = pr_req->preferredRecordSyntax;
130     }
131     else if (gdu_req && gdu_req->which == Z_GDU_Z3950 &&
132              gdu_req->u.z3950->which == Z_APDU_searchRequest)
133     {
134         sr_req = gdu_req->u.z3950->u.searchRequest;
135
136         input_syntax = sr_req->preferredRecordSyntax;
137
138         // we don't know how many hits we're going to get and therefore
139         // the effective element set name.. Therefore we can only allow
140         // two cases.. Both equal or absent.. If not, we'll just have to
141         // disable the piggyback!
142         if (sr_req->smallSetElementSetNames 
143             &&
144             sr_req->mediumSetElementSetNames
145             &&
146             sr_req->smallSetElementSetNames->which == Z_ElementSetNames_generic
147             && 
148             sr_req->mediumSetElementSetNames->which == Z_ElementSetNames_generic
149             && 
150             !strcmp(sr_req->smallSetElementSetNames->u.generic,
151                     sr_req->mediumSetElementSetNames->u.generic))
152         {
153             input_schema = sr_req->smallSetElementSetNames->u.generic;
154         }
155         else if (!sr_req->smallSetElementSetNames && 
156                  !sr_req->mediumSetElementSetNames)
157             ; // input_schema is 0 already
158         else
159         {
160             // disable piggyback (perhaps it was disabled already)
161             *sr_req->smallSetUpperBound = 0;
162             *sr_req->largeSetLowerBound = 0;
163             *sr_req->mediumSetPresentNumber = 0;
164             package.move();
165             return;
166         }
167         // we can handle it in record_transform.
168     }
169     else
170     {
171         package.move();
172         return;
173     }
174     
175     mp::odr odr_en(ODR_ENCODE);
176
177     // setting up variables for conversion state
178     yaz_record_conv_t rc = 0;
179
180     const char *match_schema = 0;
181     Odr_oid *match_syntax = 0;
182
183     const char *backend_schema = 0;
184     Odr_oid *backend_syntax = 0;
185
186     int ret_code 
187         = yaz_retrieval_request(m_retrieval,
188                                 input_schema, input_syntax,
189                                 &match_schema, &match_syntax,
190                                 &rc,
191                                 &backend_schema, &backend_syntax);
192     // error handling
193     if (ret_code != 0)
194     {
195         int error_code;
196         const char *details = 0;
197
198         if (ret_code == -1) /* error ? */
199         {
200             details = yaz_retrieval_get_error(m_retrieval);
201             error_code = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
202         }
203         else if (ret_code == 1 || ret_code == 3)
204         {
205             details = input_schema;
206             error_code = YAZ_BIB1_ELEMENT_SET_NAMES_UNSUPP;
207         }
208         else if (ret_code == 2)
209         {
210             char oidbuf[OID_STR_MAX];
211             oid_oid_to_dotstring(input_syntax, oidbuf);
212             details = odr_strdup(odr_en, oidbuf);
213             error_code = YAZ_BIB1_RECORD_SYNTAX_UNSUPP;
214         }
215         else
216         {
217             char *tmp = (char*) odr_malloc(odr_en, 80);
218             sprintf(tmp,
219                     "record_transform: yaz_retrieval_get_error returned %d",
220                     ret_code);
221             details = tmp;
222             error_code = YAZ_BIB1_UNSPECIFIED_ERROR;
223         }
224         Z_APDU *apdu;
225         if (sr_req)
226         {
227             apdu = odr_en.create_searchResponse(
228                 gdu_req->u.z3950, error_code, details);
229         }
230         else
231         {
232             apdu = odr_en.create_presentResponse(
233                 gdu_req->u.z3950, error_code, details);
234         }
235         package.response() = apdu;
236         return;
237     }
238
239     if (sr_req)
240     {
241         if (backend_syntax) 
242             sr_req->preferredRecordSyntax = odr_oiddup(odr_en, backend_syntax);
243         else
244             sr_req->preferredRecordSyntax = 0;
245
246         if (backend_schema)
247         {
248             sr_req->smallSetElementSetNames
249                 = (Z_ElementSetNames *)
250                 odr_malloc(odr_en, sizeof(Z_ElementSetNames));
251             sr_req->smallSetElementSetNames->which = Z_ElementSetNames_generic;
252             sr_req->smallSetElementSetNames->u.generic 
253                 = odr_strdup(odr_en, backend_schema);
254             sr_req->mediumSetElementSetNames = sr_req->smallSetElementSetNames;
255         }
256         else
257         {
258             sr_req->smallSetElementSetNames = 0;
259             sr_req->mediumSetElementSetNames = 0;
260         }
261     }
262     else if (pr_req)
263     {
264         if (backend_syntax) 
265             pr_req->preferredRecordSyntax = odr_oiddup(odr_en, backend_syntax);
266         else
267             pr_req->preferredRecordSyntax = 0;
268         
269         if (backend_schema)
270         {
271             pr_req->recordComposition 
272                 = (Z_RecordComposition *) 
273                 odr_malloc(odr_en, sizeof(Z_RecordComposition));
274             pr_req->recordComposition->which 
275                 = Z_RecordComp_simple;
276             pr_req->recordComposition->u.simple 
277                 = (Z_ElementSetNames *)
278                 odr_malloc(odr_en, sizeof(Z_ElementSetNames));
279             pr_req->recordComposition->u.simple->which = Z_ElementSetNames_generic;
280             pr_req->recordComposition->u.simple->u.generic 
281                 = odr_strdup(odr_en, backend_schema);
282         }
283         else
284             pr_req->recordComposition = 0;
285     }
286
287     // attaching Z3950 package to filter chain
288     package.request() = gdu_req;
289
290     package.move();
291     
292     Z_GDU *gdu_res = package.response().get();
293
294     // see if we have a records list to patch!
295     Z_NamePlusRecordList *records = 0;
296     if (gdu_res && gdu_res->which == Z_GDU_Z3950 &&
297         gdu_res->u.z3950->which == Z_APDU_presentResponse)
298     {
299         Z_PresentResponse * pr_res = gdu_res->u.z3950->u.presentResponse;
300         
301         if (rc && pr_res 
302             && pr_res->numberOfRecordsReturned 
303             && *(pr_res->numberOfRecordsReturned) > 0
304             && pr_res->records
305             && pr_res->records->which == Z_Records_DBOSD)
306         {
307             records = pr_res->records->u.databaseOrSurDiagnostics;
308         }
309     }
310     if (gdu_res && gdu_res->which == Z_GDU_Z3950 &&
311         gdu_res->u.z3950->which == Z_APDU_searchResponse)
312     {
313         Z_SearchResponse *sr_res = gdu_res->u.z3950->u.searchResponse;
314         
315         if (rc && sr_res 
316             && sr_res->numberOfRecordsReturned 
317             && *(sr_res->numberOfRecordsReturned) > 0
318             && sr_res->records
319             && sr_res->records->which == Z_Records_DBOSD)
320         {
321             records = sr_res->records->u.databaseOrSurDiagnostics;
322         }
323     }
324     
325     if (records)
326     {
327         int i;
328         for (i = 0; i < records->num_records; i++)
329         {
330             Z_NamePlusRecord *npr = records->records[i];
331             if (npr->which == Z_NamePlusRecord_databaseRecord)
332             {
333                 WRBUF output_record = wrbuf_alloc();
334                 Z_External *r = npr->u.databaseRecord;
335                 int ret_trans = 0;
336                 if (r->which == Z_External_OPAC)
337                 {
338                     ret_trans =
339                         yaz_record_conv_opac_record(rc, r->u.opac,
340                                                     output_record);
341                 }
342                 else if (r->which == Z_External_octet) 
343                 {
344                     ret_trans =
345                         yaz_record_conv_record(rc, (const char *)
346                                                r->u.octet_aligned->buf, 
347                                                r->u.octet_aligned->len,
348                                                output_record);
349                 }
350                 if (ret_trans == 0)
351                 {
352                     npr->u.databaseRecord =
353                         z_ext_record_oid(odr_en, match_syntax,
354                                          wrbuf_buf(output_record),
355                                          wrbuf_len(output_record));
356                 }
357                 else
358                 {
359                     records->records[i] =
360                         zget_surrogateDiagRec(
361                             odr_en, npr->databaseName,
362                             YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS,
363                             yaz_record_conv_get_error(rc));
364                 }
365                 wrbuf_destroy(output_record);
366             }
367         }
368         package.response() = gdu_res;
369     }
370     return;
371 }
372
373 static mp::filter::Base* filter_creator()
374 {
375     return new mp::filter::RecordTransform;
376 }
377
378 extern "C" {
379     struct metaproxy_1_filter_struct metaproxy_1_filter_record_transform = {
380         0,
381         "record_transform",
382         filter_creator
383     };
384 }
385
386
387 /*
388  * Local variables:
389  * c-basic-offset: 4
390  * c-file-style: "Stroustrup"
391  * indent-tabs-mode: nil
392  * End:
393  * vim: shiftwidth=4 tabstop=8 expandtab
394  */
395