Remove unused member
[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; /* thread pr */
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     const char *opt_sort = session_setting_oneval(sdb, PZ_SORT);
492
493     assert(link);
494
495     cl->hits = -1;
496     cl->record_offset = 0;
497     cl->diagnostic = 0;
498     client_set_state(cl, Client_Working);
499
500     if (*opt_piggyback)
501         ZOOM_connection_option_set(link, "piggyback", opt_piggyback);
502     else
503         ZOOM_connection_option_set(link, "piggyback", "1");
504     if (*opt_queryenc)
505         ZOOM_connection_option_set(link, "rpnCharset", opt_queryenc);
506     if (*opt_sru && *opt_elements)
507         ZOOM_connection_option_set(link, "schema", opt_elements);
508     else if (*opt_elements)
509         ZOOM_connection_option_set(link, "elementSetName", opt_elements);
510     if (*opt_requestsyn)
511         ZOOM_connection_option_set(link, "preferredRecordSyntax", opt_requestsyn);
512     if (*opt_maxrecs)
513         ZOOM_connection_option_set(link, "count", opt_maxrecs);
514     else
515     {
516         char n[128];
517         sprintf(n, "%d", global_parameters.toget);
518         ZOOM_connection_option_set(link, "count", n);
519     }
520     if (databaseName)
521         ZOOM_connection_option_set(link, "databaseName", databaseName);
522
523     ZOOM_connection_option_set(link, "presentChunk", "20");
524         
525     if (cl->cqlquery)
526     {
527         ZOOM_query q = ZOOM_query_create();
528         yaz_log(YLOG_LOG, "Search %s CQL: %s", sdb->database->url, cl->cqlquery);
529         ZOOM_query_cql(q, cl->cqlquery);
530         if (*opt_sort)
531             ZOOM_query_sortby(q, opt_sort);
532         rs = ZOOM_connection_search(link, q);
533         ZOOM_query_destroy(q);
534     }
535     else
536     {
537         yaz_log(YLOG_LOG, "Search %s PQF: %s", sdb->database->url, cl->pquery);
538         rs = ZOOM_connection_search_pqf(link, cl->pquery);
539     }
540     ZOOM_resultset_destroy(cl->resultset);
541     cl->resultset = rs;
542     connection_continue(co);
543 }
544
545 struct client *client_create(void)
546 {
547     struct client *r;
548     if (client_freelist)
549     {
550         r = client_freelist;
551         client_freelist = client_freelist->next;
552     }
553     else
554         r = xmalloc(sizeof(struct client));
555     r->pquery = 0;
556     r->cqlquery = 0;
557     r->database = 0;
558     r->connection = 0;
559     r->session = 0;
560     r->hits = 0;
561     r->record_offset = 0;
562     r->diagnostic = 0;
563     r->state = Client_Disconnected;
564     r->show_raw = 0;
565     r->resultset = 0;
566     r->next = 0;
567     return r;
568 }
569
570 void client_destroy(struct client *c)
571 {
572     struct session *se = c->session;
573     if (c == se->clients)
574         se->clients = c->next;
575     else
576     {
577         struct client *cc;
578         for (cc = se->clients; cc && cc->next != c; cc = cc->next)
579             ;
580         if (cc)
581             cc->next = c->next;
582     }
583     xfree(c->pquery);
584     xfree(c->cqlquery);
585
586     if (c->connection)
587         connection_release(c->connection);
588
589     ZOOM_resultset_destroy(c->resultset);
590     c->resultset = 0;
591     c->next = client_freelist;
592     client_freelist = c;
593 }
594
595 void client_set_connection(struct client *cl, struct connection *con)
596 {
597     cl->connection = con;
598 }
599
600 void client_disconnect(struct client *cl)
601 {
602     if (cl->state != Client_Idle)
603         client_set_state(cl, Client_Disconnected);
604     client_set_connection(cl, 0);
605 }
606
607 // Extract terms from query into null-terminated termlist
608 static void extract_terms(NMEM nmem, struct ccl_rpn_node *query, char **termlist)
609 {
610     int num = 0;
611
612     pull_terms(nmem, query, termlist, &num);
613     termlist[num] = 0;
614 }
615
616 // Initialize CCL map for a target
617 static CCL_bibset prepare_cclmap(struct client *cl)
618 {
619     struct session_database *sdb = client_get_database(cl);
620     struct setting *s;
621     CCL_bibset res;
622
623     if (!sdb->settings)
624         return 0;
625     res = ccl_qual_mk();
626     for (s = sdb->settings[PZ_CCLMAP]; s; s = s->next)
627     {
628         char *p = strchr(s->name + 3, ':');
629         if (!p)
630         {
631             yaz_log(YLOG_WARN, "Malformed cclmap name: %s", s->name);
632             ccl_qual_rm(&res);
633             return 0;
634         }
635         p++;
636         ccl_qual_fitem(res, s->value, p);
637     }
638     return res;
639 }
640
641 // returns a xmalloced CQL query corresponding to the pquery in client
642 static char *make_cqlquery(struct client *cl)
643 {
644     cql_transform_t cqlt = cql_transform_create();
645     Z_RPNQuery *zquery;
646     char *r;
647     WRBUF wrb = wrbuf_alloc();
648     int status;
649     ODR odr_out = odr_createmem(ODR_ENCODE);
650
651     zquery = p_query_rpn(odr_out, cl->pquery);
652     if ((status = cql_transform_rpn2cql_wrbuf(cqlt, wrb, zquery)))
653     {
654         yaz_log(YLOG_WARN, "failed to generate CQL query, code=%d", status);
655         r = 0;
656     }
657     else
658     {
659         r = xstrdup(wrbuf_cstr(wrb));
660     }     
661     wrbuf_destroy(wrb);
662     odr_destroy(odr_out);
663     cql_transform_close(cqlt);
664     return r;
665 }
666
667 // Parse the query given the settings specific to this client
668 int client_parse_query(struct client *cl, const char *query)
669 {
670     struct session *se = client_get_session(cl);
671     struct session_database *sdb = client_get_database(cl);
672     struct ccl_rpn_node *cn;
673     int cerror, cpos;
674     CCL_bibset ccl_map = prepare_cclmap(cl);
675     const char *sru = session_setting_oneval(sdb, PZ_SRU);
676     const char *pqf_prefix = session_setting_oneval(sdb, PZ_PQF_PREFIX);
677
678     if (!ccl_map)
679         return -1;
680
681     cn = ccl_find_str(ccl_map, query, &cerror, &cpos);
682     ccl_qual_rm(&ccl_map);
683     if (!cn)
684     {
685         client_set_state(cl, Client_Error);
686         yaz_log(YLOG_WARN, "Failed to parse query for %s",
687                          client_get_database(cl)->database->url);
688         return -1;
689     }
690     wrbuf_rewind(se->wrbuf);
691     if (*pqf_prefix)
692     {
693         wrbuf_puts(se->wrbuf, pqf_prefix);
694         wrbuf_puts(se->wrbuf, " ");
695     }
696     ccl_pquery(se->wrbuf, cn);
697     xfree(cl->pquery);
698     cl->pquery = xstrdup(wrbuf_cstr(se->wrbuf));
699
700     xfree(cl->cqlquery);
701     if (*sru)
702     {
703         if (!(cl->cqlquery = make_cqlquery(cl)))
704             return -1;
705     }
706     else
707         cl->cqlquery = 0;
708
709     if (!se->relevance)
710     {
711         // Initialize relevance structure with query terms
712         char *p[512];
713         extract_terms(se->nmem, cn, p);
714         se->relevance = relevance_create(
715             se->service->relevance_pct,
716             se->nmem, (const char **) p,
717             se->expected_maxrecs);
718     }
719
720     ccl_rpn_delete(cn);
721     return 0;
722 }
723
724 void client_set_session(struct client *cl, struct session *se)
725 {
726     cl->session = se;
727     cl->next = se->clients;
728     se->clients = cl;
729 }
730
731 int client_is_active(struct client *cl)
732 {
733     if (cl->connection && (cl->state == Client_Connecting ||
734                            cl->state == Client_Working))
735         return 1;
736     return 0;
737 }
738
739 struct client *client_next_in_session(struct client *cl)
740 {
741     if (cl)
742         return cl->next;
743     return 0;
744
745 }
746
747 int client_get_hits(struct client *cl)
748 {
749     return cl->hits;
750 }
751
752 int client_get_num_records(struct client *cl)
753 {
754     return cl->record_offset;
755 }
756
757 int client_get_diagnostic(struct client *cl)
758 {
759     return cl->diagnostic;
760 }
761
762 void client_set_database(struct client *cl, struct session_database *db)
763 {
764     cl->database = db;
765 }
766
767 struct host *client_get_host(struct client *cl)
768 {
769     return client_get_database(cl)->database->host;
770 }
771
772 const char *client_get_url(struct client *cl)
773 {
774     return client_get_database(cl)->database->url;
775 }
776
777 /*
778  * Local variables:
779  * c-basic-offset: 4
780  * c-file-style: "Stroustrup"
781  * indent-tabs-mode: nil
782  * End:
783  * vim: shiftwidth=4 tabstop=8 expandtab
784  */
785