31c254337d9d5989498b4be7c01d5e30952e6deb
[pazpar2-moved-to-github.git] / src / client.c
1 /* This file is part of Pazpar2.
2    Copyright (C) 2006-2009 Index Data
3
4 Pazpar2 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 Pazpar2 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
20 /** \file client.c
21     \brief Z39.50 client 
22 */
23
24 #if HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <string.h>
31 #if HAVE_SYS_TIME_H
32 #include <sys/time.h>
33 #endif
34 #if HAVE_UNISTD_H
35 #include <unistd.h>
36 #endif
37 #include <signal.h>
38 #include <assert.h>
39
40 #include <yaz/marcdisp.h>
41 #include <yaz/comstack.h>
42 #include <yaz/tcpip.h>
43 #include <yaz/proto.h>
44 #include <yaz/readconf.h>
45 #include <yaz/pquery.h>
46 #include <yaz/otherinfo.h>
47 #include <yaz/yaz-util.h>
48 #include <yaz/nmem.h>
49 #include <yaz/query-charset.h>
50 #include <yaz/querytowrbuf.h>
51 #include <yaz/oid_db.h>
52 #include <yaz/diagbib1.h>
53 #include <yaz/snprintf.h>
54 #include <yaz/rpn2cql.h>
55
56 #define USE_TIMING 0
57 #if USE_TIMING
58 #include <yaz/timing.h>
59 #endif
60
61 #include "pazpar2.h"
62
63 #include "client.h"
64 #include "connection.h"
65 #include "settings.h"
66
67 /** \brief Represents client state for a connection to one search target */
68 struct client {
69     struct session_database *database;
70     struct connection *connection;
71     struct session *session;
72     char *pquery; // Current search
73     char *cqlquery; // used for SRU targets only
74     int hits;
75     int record_offset;
76     int diagnostic;
77     enum client_state state;
78     struct show_raw *show_raw;
79     struct client *next;     // next client in session or next in free list
80     ZOOM_resultset resultset;
81 };
82
83 struct show_raw {
84     int active; // whether this request has been sent to the server
85     int position;
86     int binary;
87     char *syntax;
88     char *esn;
89     void (*error_handler)(void *data, const char *addinfo);
90     void (*record_handler)(void *data, const char *buf, size_t sz);
91     void *data;
92     struct show_raw *next;
93 };
94
95 static const char *client_states[] = {
96     "Client_Connecting",
97     "Client_Idle",
98     "Client_Working",
99     "Client_Error",
100     "Client_Failed",
101     "Client_Disconnected"
102 };
103
104 static struct client *client_freelist = 0;
105
106 const char *client_get_state_str(struct client *cl)
107 {
108     return client_states[cl->state];
109 }
110
111 enum client_state client_get_state(struct client *cl)
112 {
113     return cl->state;
114 }
115
116 void client_set_state(struct client *cl, enum client_state st)
117 {
118     cl->state = st;
119     if (cl->session)
120     {
121         int no_active = session_active_clients(cl->session);
122         if (no_active == 0)
123             session_alert_watch(cl->session, SESSION_WATCH_SHOW);
124     }
125 }
126
127 static void client_show_raw_error(struct client *cl, const char *addinfo);
128
129 // Close connection and set state to error
130 void client_fatal(struct client *cl)
131 {
132     yaz_log(YLOG_WARN, "Fatal error from %s", client_get_url(cl));
133     connection_destroy(cl->connection);
134     client_set_state(cl, Client_Error);
135 }
136
137 struct connection *client_get_connection(struct client *cl)
138 {
139     return cl->connection;
140 }
141
142 struct session_database *client_get_database(struct client *cl)
143 {
144     return cl->database;
145 }
146
147 struct session *client_get_session(struct client *cl)
148 {
149     return cl->session;
150 }
151
152 const char *client_get_pquery(struct client *cl)
153 {
154     return cl->pquery;
155 }
156
157 static void client_send_raw_present(struct client *cl);
158 static int nativesyntax_to_type(struct session_database *sdb, char *type,
159                                 ZOOM_record rec);
160
161 static void client_show_immediate(
162     ZOOM_resultset resultset, struct session_database *sdb, int position,
163     void *data,
164     void (*error_handler)(void *data, const char *addinfo),
165     void (*record_handler)(void *data, const char *buf, size_t sz),
166     int binary)
167 {
168     ZOOM_record rec = 0;
169     char type[80];
170     const char *buf;
171     int len;
172
173     if (!resultset)
174     {
175         error_handler(data, "no resultset");
176         return;
177     }
178     rec = ZOOM_resultset_record(resultset, position-1);
179     if (!rec)
180     {
181         error_handler(data, "no record");
182         return;
183     }
184     if (binary)
185         strcpy(type, "raw");
186     else
187         nativesyntax_to_type(sdb, type, rec);
188     buf = ZOOM_record_get(rec, type, &len);
189     if (!buf)
190     {
191         error_handler(data, "no record");
192         return;
193     }
194     record_handler(data, buf, len);
195 }
196
197
198 int client_show_raw_begin(struct client *cl, int position,
199                           const char *syntax, const char *esn,
200                           void *data,
201                           void (*error_handler)(void *data, const char *addinfo),
202                           void (*record_handler)(void *data, const char *buf,
203                                                  size_t sz),
204                           int binary)
205 {
206     if (syntax == 0 && esn == 0)
207         client_show_immediate(cl->resultset, client_get_database(cl),
208                               position, data,
209                               error_handler, record_handler,
210                               binary);
211     else
212     {
213         struct show_raw *rr, **rrp;
214
215         if (!cl->connection)
216             return -1;
217     
218
219         rr = xmalloc(sizeof(*rr));
220         rr->position = position;
221         rr->active = 0;
222         rr->data = data;
223         rr->error_handler = error_handler;
224         rr->record_handler = record_handler;
225         rr->binary = binary;
226         if (syntax)
227             rr->syntax = xstrdup(syntax);
228         else
229             rr->syntax = 0;
230         if (esn)
231             rr->esn = xstrdup(esn);
232         else
233             rr->esn = 0;
234         rr->next = 0;
235         
236         for (rrp = &cl->show_raw; *rrp; rrp = &(*rrp)->next)
237             ;
238         *rrp = rr;
239         
240         if (cl->state == Client_Failed)
241         {
242             client_show_raw_error(cl, "client failed");
243         }
244         else if (cl->state == Client_Disconnected)
245         {
246             client_show_raw_error(cl, "client disconnected");
247         }
248         else
249         {
250             client_send_raw_present(cl);
251         }
252     }
253     return 0;
254 }
255
256 void client_show_raw_remove(struct client *cl, void *data)
257 {
258     struct show_raw *rr = data;
259     struct show_raw **rrp = &cl->show_raw;
260     while (*rrp != rr)
261         rrp = &(*rrp)->next;
262     if (*rrp)
263     {
264         *rrp = rr->next;
265         xfree(rr);
266     }
267 }
268
269 void client_show_raw_dequeue(struct client *cl)
270 {
271     struct show_raw *rr = cl->show_raw;
272
273     cl->show_raw = rr->next;
274     xfree(rr);
275 }
276
277 static void client_show_raw_error(struct client *cl, const char *addinfo)
278 {
279     while (cl->show_raw)
280     {
281         cl->show_raw->error_handler(cl->show_raw->data, addinfo);
282         client_show_raw_dequeue(cl);
283     }
284 }
285
286 static void client_send_raw_present(struct client *cl)
287 {
288     struct session_database *sdb = client_get_database(cl);
289     struct connection *co = client_get_connection(cl);
290     ZOOM_resultset set = cl->resultset;
291
292     int offset = cl->show_raw->position;
293     const char *syntax = 0;
294     const char *elements = 0;
295
296     assert(cl->show_raw);
297     assert(set);
298
299     yaz_log(YLOG_DEBUG, "%s: trying to present %d record(s) from %d",
300             client_get_url(cl), 1, offset);
301
302     if (cl->show_raw->syntax)
303         syntax = cl->show_raw->syntax;
304     else
305         syntax = session_setting_oneval(sdb, PZ_REQUESTSYNTAX);
306     ZOOM_resultset_option_set(set, "preferredRecordSyntax", syntax);
307
308     if (cl->show_raw->esn)
309         elements = cl->show_raw->esn;
310     else
311         elements = session_setting_oneval(sdb, PZ_ELEMENTS);
312     if (elements && *elements)
313         ZOOM_resultset_option_set(set, "elementSetName", elements);
314
315     ZOOM_resultset_records(set, 0, offset-1, 1);
316     cl->show_raw->active = 1;
317
318     connection_continue(co);
319 }
320
321 static int nativesyntax_to_type(struct session_database *sdb, char *type,
322                                 ZOOM_record rec)
323 {
324     const char *s = session_setting_oneval(sdb, PZ_NATIVESYNTAX);
325
326     if (s && *s)
327     {
328         if (!strncmp(s, "iso2709", 7))
329         {
330             const char *cp = strchr(s, ';');
331             yaz_snprintf(type, 80, "xml; charset=%s", cp ? cp+1 : "marc-8s");
332         }
333         else if (!strncmp(s, "xml", 3))
334         {
335             strcpy(type, "xml");
336         }
337         else
338             return -1;
339         return 0;
340     }
341     else  /* attempt to deduce structure */
342     {
343         const char *syntax = ZOOM_record_get(rec, "syntax", NULL);
344         if (syntax)
345         {
346             if (!strcmp(syntax, "XML"))
347             {
348                 strcpy(type, "xml");
349                 return 0;
350             }
351             else if (!strcmp(syntax, "USmarc") || !strcmp(syntax, "MARC21"))
352             {
353                 strcpy(type, "xml; charset=marc8-s");
354                 return 0;
355             }
356             else return -1;
357         }
358         else return -1;
359     }
360 }
361
362 static void ingest_raw_record(struct client *cl, ZOOM_record rec)
363 {
364     const char *buf;
365     int len;
366     char type[80];
367
368     if (cl->show_raw->binary)
369         strcpy(type, "raw");
370     else
371     {
372         struct session_database *sdb = client_get_database(cl);
373         nativesyntax_to_type(sdb, type, rec);
374     }
375
376     buf = ZOOM_record_get(rec, type, &len);
377     cl->show_raw->record_handler(cl->show_raw->data,  buf, len);
378     client_show_raw_dequeue(cl);
379 }
380
381 void client_search_response(struct client *cl)
382 {
383     struct connection *co = cl->connection;
384     struct session *se = cl->session;
385     ZOOM_connection link = connection_get_link(co);
386     ZOOM_resultset resultset = cl->resultset;
387     const char *error, *addinfo;
388
389     if (ZOOM_connection_error(link, &error, &addinfo))
390     {
391         cl->hits = 0;
392         client_set_state(cl, Client_Error);
393         yaz_log(YLOG_WARN, "Search error %s (%s): %s",
394             error, addinfo, client_get_url(cl));
395     }
396     else
397     {
398         cl->record_offset = 0;
399         cl->hits = ZOOM_resultset_size(resultset);
400         se->total_hits += cl->hits;
401     }
402 }
403
404
405 void client_record_response(struct client *cl)
406 {
407     struct connection *co = cl->connection;
408     ZOOM_connection link = connection_get_link(co);
409     ZOOM_resultset resultset = cl->resultset;
410     const char *error, *addinfo;
411
412     if (ZOOM_connection_error(link, &error, &addinfo))
413     {
414         client_set_state(cl, Client_Error);
415         yaz_log(YLOG_WARN, "Search error %s (%s): %s",
416             error, addinfo, client_get_url(cl));
417     }
418     else
419     {
420         ZOOM_record rec = 0;
421         const char *msg, *addinfo;
422         
423         if (cl->show_raw && cl->show_raw->active)
424         {
425             if ((rec = ZOOM_resultset_record(resultset,
426                                              cl->show_raw->position-1)))
427             {
428                 cl->show_raw->active = 0;
429                 ingest_raw_record(cl, rec);
430             }
431             else
432             {
433                 yaz_log(YLOG_WARN, "Expected record, but got NULL, offset=%d",
434                         cl->show_raw->position-1);
435             }
436         }
437         else
438         {
439             int offset = cl->record_offset;
440             if ((rec = ZOOM_resultset_record(resultset, offset)))
441             {
442                 cl->record_offset++;
443                 if (ZOOM_record_error(rec, &msg, &addinfo, 0))
444                     yaz_log(YLOG_WARN, "Record error %s (%s): %s (rec #%d)",
445                             error, addinfo, client_get_url(cl),
446                             cl->record_offset);
447                 else
448                 {
449                     struct session_database *sdb = client_get_database(cl);
450                     const char *xmlrec;
451                     char type[80];
452                     if (nativesyntax_to_type(sdb, type, rec))
453                         yaz_log(YLOG_WARN, "Failed to determine record type");
454                     if ((xmlrec = ZOOM_record_get(rec, type, NULL)))
455                     {
456                         if (ingest_record(cl, xmlrec, cl->record_offset))
457                         {
458                             session_alert_watch(cl->session, SESSION_WATCH_SHOW);
459                             session_alert_watch(cl->session, SESSION_WATCH_RECORD);
460                         }
461                         else
462                             yaz_log(YLOG_WARN, "Failed to ingest");
463                     }
464                     else
465                         yaz_log(YLOG_WARN, "Failed to extract ZOOM record");
466                 }
467
468             }
469             else
470             {
471                 yaz_log(YLOG_WARN, "Expected record, but got NULL, offset=%d",
472                         offset);
473             }
474         }
475     }
476 }
477
478 void client_start_search(struct client *cl)
479 {
480     struct session_database *sdb = client_get_database(cl);
481     struct connection *co = client_get_connection(cl);
482     ZOOM_connection link = connection_get_link(co);
483     ZOOM_resultset rs;
484     char *databaseName = sdb->database->databases[0];
485     const char *opt_piggyback = session_setting_oneval(sdb, PZ_PIGGYBACK);
486     const char *opt_queryenc = session_setting_oneval(sdb, PZ_QUERYENCODING);
487     const char *opt_elements = session_setting_oneval(sdb, PZ_ELEMENTS);
488     const char *opt_requestsyn = session_setting_oneval(sdb, PZ_REQUESTSYNTAX);
489     const char *opt_maxrecs = session_setting_oneval(sdb, PZ_MAXRECS);
490     const char *opt_sru = session_setting_oneval(sdb, PZ_SRU);
491
492     assert(link);
493
494     cl->hits = -1;
495     cl->record_offset = 0;
496     cl->diagnostic = 0;
497     client_set_state(cl, Client_Working);
498
499     if (*opt_piggyback)
500         ZOOM_connection_option_set(link, "piggyback", opt_piggyback);
501     else
502         ZOOM_connection_option_set(link, "piggyback", "1");
503     if (*opt_queryenc)
504         ZOOM_connection_option_set(link, "rpnCharset", opt_queryenc);
505     if (*opt_sru && *opt_elements)
506         ZOOM_connection_option_set(link, "schema", opt_elements);
507     else if (*opt_elements)
508         ZOOM_connection_option_set(link, "elementSetName", opt_elements);
509     if (*opt_requestsyn)
510         ZOOM_connection_option_set(link, "preferredRecordSyntax", opt_requestsyn);
511     if (*opt_maxrecs)
512         ZOOM_connection_option_set(link, "count", opt_maxrecs);
513     else
514     {
515         char n[128];
516         sprintf(n, "%d", global_parameters.toget);
517         ZOOM_connection_option_set(link, "count", n);
518     }
519     if (databaseName)
520         ZOOM_connection_option_set(link, "databaseName", databaseName);
521
522     ZOOM_connection_option_set(link, "presentChunk", "20");
523         
524     if (cl->cqlquery)
525     {
526         ZOOM_query q = ZOOM_query_create();
527         yaz_log(YLOG_LOG, "Search %s CQL: %s", sdb->database->url, cl->cqlquery);
528         ZOOM_query_cql(q, cl->cqlquery);
529         rs = ZOOM_connection_search(link, q);
530         ZOOM_query_destroy(q);
531     }
532     else
533     {
534         yaz_log(YLOG_LOG, "Search %s PQF: %s", sdb->database->url, cl->pquery);
535         rs = ZOOM_connection_search_pqf(link, cl->pquery);
536     }
537     ZOOM_resultset_destroy(cl->resultset);
538     cl->resultset = rs;
539     connection_continue(co);
540 }
541
542 struct client *client_create(void)
543 {
544     struct client *r;
545     if (client_freelist)
546     {
547         r = client_freelist;
548         client_freelist = client_freelist->next;
549     }
550     else
551         r = xmalloc(sizeof(struct client));
552     r->pquery = 0;
553     r->cqlquery = 0;
554     r->database = 0;
555     r->connection = 0;
556     r->session = 0;
557     r->hits = 0;
558     r->record_offset = 0;
559     r->diagnostic = 0;
560     r->state = Client_Disconnected;
561     r->show_raw = 0;
562     r->resultset = 0;
563     r->next = 0;
564     return r;
565 }
566
567 void client_destroy(struct client *c)
568 {
569     struct session *se = c->session;
570     if (c == se->clients)
571         se->clients = c->next;
572     else
573     {
574         struct client *cc;
575         for (cc = se->clients; cc && cc->next != c; cc = cc->next)
576             ;
577         if (cc)
578             cc->next = c->next;
579     }
580     xfree(c->pquery);
581     xfree(c->cqlquery);
582
583     if (c->connection)
584         connection_release(c->connection);
585
586     ZOOM_resultset_destroy(c->resultset);
587     c->resultset = 0;
588     c->next = client_freelist;
589     client_freelist = c;
590 }
591
592 void client_set_connection(struct client *cl, struct connection *con)
593 {
594     cl->connection = con;
595 }
596
597 void client_disconnect(struct client *cl)
598 {
599     if (cl->state != Client_Idle)
600         client_set_state(cl, Client_Disconnected);
601     client_set_connection(cl, 0);
602 }
603
604 // Extract terms from query into null-terminated termlist
605 static void extract_terms(NMEM nmem, struct ccl_rpn_node *query, char **termlist)
606 {
607     int num = 0;
608
609     pull_terms(nmem, query, termlist, &num);
610     termlist[num] = 0;
611 }
612
613 // Initialize CCL map for a target
614 static CCL_bibset prepare_cclmap(struct client *cl)
615 {
616     struct session_database *sdb = client_get_database(cl);
617     struct setting *s;
618     CCL_bibset res;
619
620     if (!sdb->settings)
621         return 0;
622     res = ccl_qual_mk();
623     for (s = sdb->settings[PZ_CCLMAP]; s; s = s->next)
624     {
625         char *p = strchr(s->name + 3, ':');
626         if (!p)
627         {
628             yaz_log(YLOG_WARN, "Malformed cclmap name: %s", s->name);
629             ccl_qual_rm(&res);
630             return 0;
631         }
632         p++;
633         ccl_qual_fitem(res, s->value, p);
634     }
635     return res;
636 }
637
638 // returns a xmalloced CQL query corresponding to the pquery in client
639 static char *make_cqlquery(struct client *cl)
640 {
641     cql_transform_t cqlt = cql_transform_create();
642     Z_RPNQuery *zquery;
643     char *r;
644     WRBUF wrb = wrbuf_alloc();
645     int status;
646
647     zquery = p_query_rpn(global_parameters.odr_out, cl->pquery);
648     if ((status = cql_transform_rpn2cql_wrbuf(cqlt, wrb, zquery)))
649     {
650         yaz_log(YLOG_WARN, "failed to generate CQL query, code=%d", status);
651         return 0;
652     }
653     r = xstrdup(wrbuf_cstr(wrb));
654
655     wrbuf_destroy(wrb);
656     odr_reset(global_parameters.odr_out); // releases the zquery
657     cql_transform_close(cqlt);
658     return r;
659 }
660
661 // Parse the query given the settings specific to this client
662 int client_parse_query(struct client *cl, const char *query)
663 {
664     struct session *se = client_get_session(cl);
665     struct session_database *sdb = client_get_database(cl);
666     struct ccl_rpn_node *cn;
667     int cerror, cpos;
668     CCL_bibset ccl_map = prepare_cclmap(cl);
669     const char *sru = session_setting_oneval(sdb, PZ_SRU);
670     const char *pqf_prefix = session_setting_oneval(sdb, PZ_PQF_PREFIX);
671
672     if (!ccl_map)
673         return -1;
674
675     cn = ccl_find_str(ccl_map, query, &cerror, &cpos);
676     ccl_qual_rm(&ccl_map);
677     if (!cn)
678     {
679         client_set_state(cl, Client_Error);
680         yaz_log(YLOG_WARN, "Failed to parse query for %s",
681                          client_get_database(cl)->database->url);
682         return -1;
683     }
684     wrbuf_rewind(se->wrbuf);
685     if (*pqf_prefix)
686     {
687         wrbuf_puts(se->wrbuf, pqf_prefix);
688         wrbuf_puts(se->wrbuf, " ");
689     }
690     ccl_pquery(se->wrbuf, cn);
691     xfree(cl->pquery);
692     cl->pquery = xstrdup(wrbuf_cstr(se->wrbuf));
693
694     xfree(cl->cqlquery);
695     if (*sru)
696     {
697         if (!(cl->cqlquery = make_cqlquery(cl)))
698             return -1;
699     }
700     else
701         cl->cqlquery = 0;
702
703     if (!se->relevance)
704     {
705         // Initialize relevance structure with query terms
706         char *p[512];
707         extract_terms(se->nmem, cn, p);
708         se->relevance = relevance_create(
709             global_parameters.server->relevance_pct,
710             se->nmem, (const char **) p,
711             se->expected_maxrecs);
712     }
713
714     ccl_rpn_delete(cn);
715     return 0;
716 }
717
718 void client_set_session(struct client *cl, struct session *se)
719 {
720     cl->session = se;
721     cl->next = se->clients;
722     se->clients = cl;
723 }
724
725 int client_is_active(struct client *cl)
726 {
727     if (cl->connection && (cl->state == Client_Connecting ||
728                            cl->state == Client_Working))
729         return 1;
730     return 0;
731 }
732
733 struct client *client_next_in_session(struct client *cl)
734 {
735     if (cl)
736         return cl->next;
737     return 0;
738
739 }
740
741 int client_get_hits(struct client *cl)
742 {
743     return cl->hits;
744 }
745
746 int client_get_num_records(struct client *cl)
747 {
748     return cl->record_offset;
749 }
750
751 int client_get_diagnostic(struct client *cl)
752 {
753     return cl->diagnostic;
754 }
755
756 void client_set_database(struct client *cl, struct session_database *db)
757 {
758     cl->database = db;
759 }
760
761 struct host *client_get_host(struct client *cl)
762 {
763     return client_get_database(cl)->database->host;
764 }
765
766 const char *client_get_url(struct client *cl)
767 {
768     return client_get_database(cl)->database->url;
769 }
770
771 /*
772  * Local variables:
773  * c-basic-offset: 4
774  * c-file-style: "Stroustrup"
775  * indent-tabs-mode: nil
776  * End:
777  * vim: shiftwidth=4 tabstop=8 expandtab
778  */
779