Fixed bug #1646. http error: when trying to callpz2.
[pazpar2-moved-to-github.git] / src / client.c
1 /* $Id: client.c,v 1.26 2007-10-02 10:11:56 adam Exp $
2    Copyright (c) 2006-2007, Index Data.
3
4 This file is part of Pazpar2.
5
6 Pazpar2 is free software; you can redistribute it and/or modify it under
7 the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 2, or (at your option) any later
9 version.
10
11 Pazpar2 is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Pazpar2; see the file LICENSE.  If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19 02111-1307, USA.
20  */
21
22 /** \file client.c
23     \brief Z39.50 client 
24 */
25
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <sys/time.h>
30 #include <unistd.h>
31 #include <sys/socket.h>
32 #include <netdb.h>
33 #include <signal.h>
34 #include <ctype.h>
35 #include <assert.h>
36
37 #include <yaz/marcdisp.h>
38 #include <yaz/comstack.h>
39 #include <yaz/tcpip.h>
40 #include <yaz/proto.h>
41 #include <yaz/readconf.h>
42 #include <yaz/pquery.h>
43 #include <yaz/otherinfo.h>
44 #include <yaz/yaz-util.h>
45 #include <yaz/nmem.h>
46 #include <yaz/query-charset.h>
47 #include <yaz/querytowrbuf.h>
48 #include <yaz/oid_db.h>
49 #include <yaz/diagbib1.h>
50
51 #if HAVE_CONFIG_H
52 #include "cconfig.h"
53 #endif
54
55 #define USE_TIMING 0
56 #if USE_TIMING
57 #include <yaz/timing.h>
58 #endif
59
60 #include <netinet/in.h>
61
62 #include "pazpar2.h"
63
64 #include "client.h"
65 #include "connection.h"
66 #include "settings.h"
67
68 /** \brief Represents client state for a connection to one search target */
69 struct client {
70     struct session_database *database;
71     struct connection *connection;
72     struct session *session;
73     char *pquery; // Current search
74     int hits;
75     int records;
76     int setno;
77     int requestid;            // ID of current outstanding request
78     int diagnostic;
79     enum client_state state;
80     struct show_raw *show_raw;
81     struct client *next;     // next client in session or next in free list
82 };
83
84 struct show_raw {
85     int active; // whether this request has been sent to the server
86     int position;
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 };
93
94 static const char *client_states[] = {
95     "Client_Connecting",
96     "Client_Connected",
97     "Client_Idle",
98     "Client_Initializing",
99     "Client_Searching",
100     "Client_Presenting",
101     "Client_Error",
102     "Client_Failed",
103     "Client_Disconnected",
104     "Client_Stopped",
105     "Client_Continue"
106 };
107
108 static struct client *client_freelist = 0;
109
110 static int send_apdu(struct client *c, Z_APDU *a)
111 {
112     struct session_database *sdb = client_get_database(c);
113     const char *apdulog = session_setting_oneval(sdb, PZ_APDULOG);
114     if (apdulog && *apdulog && *apdulog != '0')
115     {
116         ODR p = odr_createmem(ODR_PRINT);
117         yaz_log(YLOG_LOG, "send APDU %s", client_get_url(c));
118
119         odr_setprint(p, yaz_log_file());
120         z_APDU(p, &a, 0, 0);
121         odr_setprint(p, stderr);
122         odr_destroy(p);
123     }
124     return connection_send_apdu(client_get_connection(c), a);
125 }
126
127
128 const char *client_get_state_str(struct client *cl)
129 {
130     return client_states[cl->state];
131 }
132
133 enum client_state client_get_state(struct client *cl)
134 {
135     return cl->state;
136 }
137
138 void client_set_state(struct client *cl, enum client_state st)
139 {
140     cl->state = st;
141     if (cl->session)
142     {
143         int no_active = session_active_clients(cl->session);
144         if (no_active == 0)
145             session_alert_watch(cl->session, SESSION_WATCH_SHOW);
146     }
147 }
148
149 static void client_show_raw_error(struct client *cl, const char *addinfo);
150
151 // Close connection and set state to error
152 void client_fatal(struct client *cl)
153 {
154     client_show_raw_error(cl, "client connection failure");
155     yaz_log(YLOG_WARN, "Fatal error from %s", client_get_url(cl));
156     connection_destroy(cl->connection);
157     client_set_state(cl, Client_Error);
158 }
159
160
161 static int diag_to_wrbuf(Z_DiagRec **pp, int num, WRBUF w)
162 {
163     int code = 0;
164     int i;
165     for (i = 0; i<num; i++)
166     {
167         Z_DiagRec *p = pp[i];
168         if (i)
169             wrbuf_puts(w, "; ");
170         if (p->which != Z_DiagRec_defaultFormat)
171         {
172             wrbuf_puts(w, "? Not in default format");
173         }
174         else
175         {
176             Z_DefaultDiagFormat *r = p->u.defaultFormat;
177             
178             if (!r->diagnosticSetId)
179                 wrbuf_puts(w, "? Missing diagset");
180             else
181             {
182                 oid_class oclass;
183                 char diag_name_buf[OID_STR_MAX];
184                 const char *diag_name = 0;
185                 diag_name = yaz_oid_to_string_buf
186                     (r->diagnosticSetId, &oclass, diag_name_buf);
187                 wrbuf_puts(w, diag_name);
188             }
189             if (!code)
190                 code = *r->condition;
191             wrbuf_printf(w, " %d %s", *r->condition,
192                          diagbib1_str(*r->condition));
193             switch (r->which)
194             {
195             case Z_DefaultDiagFormat_v2Addinfo:
196                 wrbuf_printf(w, " -- v2 addinfo '%s'", r->u.v2Addinfo);
197                 break;
198             case Z_DefaultDiagFormat_v3Addinfo:
199                 wrbuf_printf(w, " -- v3 addinfo '%s'", r->u.v3Addinfo);
200                 break;
201             }
202         }
203     }
204     return code;
205 }
206
207
208
209 struct connection *client_get_connection(struct client *cl)
210 {
211     return cl->connection;
212 }
213
214 struct session_database *client_get_database(struct client *cl)
215 {
216     return cl->database;
217 }
218
219 struct session *client_get_session(struct client *cl)
220 {
221     return cl->session;
222 }
223
224 const char *client_get_pquery(struct client *cl)
225 {
226     return cl->pquery;
227 }
228
229 void client_set_requestid(struct client *cl, int id)
230 {
231     cl->requestid = id;
232 }
233
234 int client_show_raw_begin(struct client *cl, int position,
235                           const char *syntax, const char *esn,
236                           void *data,
237                           void (*error_handler)(void *data, const char *addinfo),
238                           void (*record_handler)(void *data, const char *buf,
239                                                  size_t sz))
240 {
241     if (cl->show_raw)
242     {   /* raw show already in progress */
243         return -1;
244     }
245     if (!cl->connection)
246     {   /* the client has no connection */
247         return -2;
248     }
249     cl->show_raw = xmalloc(sizeof(*cl->show_raw));
250     cl->show_raw->position = position;
251     cl->show_raw->active = 0;
252     cl->show_raw->data = data;
253     cl->show_raw->error_handler = error_handler;
254     cl->show_raw->record_handler = record_handler;
255     if (syntax)
256         cl->show_raw->syntax = xstrdup(syntax);
257     else
258         cl->show_raw->syntax = 0;
259     if (esn)
260         cl->show_raw->esn = xstrdup(esn);
261     else
262         cl->show_raw->esn = 0;
263     
264
265     if (cl->state == Client_Failed)
266     {
267         client_show_raw_error(cl, "client failed");
268     }
269     else if (cl->state == Client_Disconnected)
270     {
271         client_show_raw_error(cl, "client disconnected");
272     }
273     else
274     {
275         client_continue(cl);
276     }
277     return 0;
278 }
279
280 void client_show_raw_reset(struct client *cl)
281 {
282     xfree(cl->show_raw);
283     cl->show_raw = 0;
284 }
285
286 static void client_show_raw_error(struct client *cl, const char *addinfo)
287 {
288     if (cl->show_raw)
289     {
290         cl->show_raw->error_handler(cl->show_raw->data, addinfo);
291         client_show_raw_reset(cl);
292     }
293 }
294
295 static void client_show_raw_cancel(struct client *cl)
296 {
297     if (cl->show_raw)
298     {
299         cl->show_raw->error_handler(cl->show_raw->data, "cancel");
300         client_show_raw_reset(cl);
301     }
302 }
303
304 static void client_present_syntax(Z_APDU *a, const char *syntax)
305 {
306     // empty string for syntax OMITS preferredRecordSyntax (OPTIONAL)
307     if (syntax && *syntax)
308         a->u.presentRequest->preferredRecordSyntax =
309             yaz_string_to_oid_odr(yaz_oid_std(),
310                                   CLASS_RECSYN, syntax,
311                                   global_parameters.odr_out);
312 }
313
314 static void client_present_elements(Z_APDU *a, const char *elements)
315 {
316     if (elements && *elements)  // element set is optional
317     {
318         Z_ElementSetNames *elementSetNames =
319             odr_malloc(global_parameters.odr_out, sizeof(*elementSetNames));
320         Z_RecordComposition *compo = 
321             odr_malloc(global_parameters.odr_out, sizeof(*compo));
322         a->u.presentRequest->recordComposition = compo;
323
324         compo->which = Z_RecordComp_simple;
325         compo->u.simple = elementSetNames;
326
327         elementSetNames->which = Z_ElementSetNames_generic;
328         elementSetNames->u.generic = 
329             odr_strdup(global_parameters.odr_out, elements);
330     }
331 }
332
333 void client_send_raw_present(struct client *cl)
334 {
335     struct session_database *sdb = client_get_database(cl);
336     Z_APDU *a = zget_APDU(global_parameters.odr_out, Z_APDU_presentRequest);
337     int toget = 1;
338     int start = cl->show_raw->position;
339     const char *syntax = 0;
340     const char *elements = 0;
341
342     assert(cl->show_raw);
343
344     yaz_log(YLOG_DEBUG, "%s: trying to present %d record(s) from %d",
345             client_get_url(cl), toget, start);
346
347     a->u.presentRequest->resultSetStartPoint = &start;
348     a->u.presentRequest->numberOfRecordsRequested = &toget;
349
350     if (cl->show_raw->syntax)
351         syntax = cl->show_raw->syntax;
352     else
353         syntax = session_setting_oneval(sdb, PZ_REQUESTSYNTAX);
354
355     client_present_syntax(a, syntax);
356     if (cl->show_raw->esn)
357         elements = cl->show_raw->esn;
358     else
359         elements = session_setting_oneval(sdb, PZ_ELEMENTS);
360     client_present_elements(a, elements);
361
362     if (send_apdu(cl, a) >= 0)
363     {
364         cl->show_raw->active = 1;
365         cl->state = Client_Presenting;
366     }
367     else
368     {
369         client_show_raw_error(cl, "send_apdu failed");
370         cl->state = Client_Error;
371     }
372     odr_reset(global_parameters.odr_out);
373 }
374
375 void client_send_present(struct client *cl)
376 {
377     struct session_database *sdb = client_get_database(cl);
378     Z_APDU *a = zget_APDU(global_parameters.odr_out, Z_APDU_presentRequest);
379     int toget;
380     int start = cl->records + 1;
381     const char *syntax = 0;
382     const char *elements = 0;
383
384     toget = global_parameters.chunk;
385     if (toget > global_parameters.toget - cl->records)
386         toget = global_parameters.toget - cl->records;
387     if (toget > cl->hits - cl->records)
388         toget = cl->hits - cl->records;
389
390     yaz_log(YLOG_DEBUG, "Trying to present %d record(s) from %d",
391             toget, start);
392
393     a->u.presentRequest->resultSetStartPoint = &start;
394     a->u.presentRequest->numberOfRecordsRequested = &toget;
395
396     syntax = session_setting_oneval(sdb, PZ_REQUESTSYNTAX);
397     client_present_syntax(a, syntax);
398
399     elements = session_setting_oneval(sdb, PZ_ELEMENTS);
400     client_present_elements(a, elements);
401
402     if (send_apdu(cl, a) >= 0)
403         cl->state = Client_Presenting;
404     else
405         cl->state = Client_Error;
406     odr_reset(global_parameters.odr_out);
407 }
408
409
410 void client_send_search(struct client *cl)
411 {
412     struct session *se = client_get_session(cl);
413     struct session_database *sdb = client_get_database(cl);
414     Z_APDU *a = zget_APDU(global_parameters.odr_out, Z_APDU_searchRequest);
415     int ndb;
416     char **databaselist;
417     Z_Query *zquery;
418     int ssub = 0, lslb = 100000, mspn = 10;
419     const char *piggyback = session_setting_oneval(sdb, PZ_PIGGYBACK);
420     const char *queryenc = session_setting_oneval(sdb, PZ_QUERYENCODING);
421
422     yaz_log(YLOG_DEBUG, "Sending search to %s", sdb->database->url);
423
424     
425     // constructing RPN query
426     a->u.searchRequest->query = zquery = odr_malloc(global_parameters.odr_out,
427                                                     sizeof(Z_Query));
428     zquery->which = Z_Query_type_1;
429     zquery->u.type_1 = p_query_rpn(global_parameters.odr_out, 
430                                    client_get_pquery(cl));
431
432     // converting to target encoding
433     if (queryenc && *queryenc)
434     {
435         yaz_iconv_t iconv = yaz_iconv_open(queryenc, "UTF-8");
436         if (iconv){
437             yaz_query_charset_convert_rpnquery(zquery->u.type_1, 
438                                                global_parameters.odr_out, 
439                                                iconv);
440             yaz_iconv_close(iconv);
441         } else
442             yaz_log(YLOG_WARN, "Query encoding failed %s %s", 
443                     client_get_database(cl)->database->url, queryenc);
444     }
445
446     for (ndb = 0; sdb->database->databases[ndb]; ndb++)
447         ;
448     databaselist = odr_malloc(global_parameters.odr_out, sizeof(char*) * ndb);
449     for (ndb = 0; sdb->database->databases[ndb]; ndb++)
450         databaselist[ndb] = sdb->database->databases[ndb];
451
452     if (!piggyback || *piggyback == '1')
453     {
454         const char *elements = session_setting_oneval(sdb, PZ_ELEMENTS);
455         const char *recsyn = session_setting_oneval(sdb, PZ_REQUESTSYNTAX);
456         if (recsyn && *recsyn)
457         {
458             a->u.searchRequest->preferredRecordSyntax =
459                 yaz_string_to_oid_odr(yaz_oid_std(),
460                                       CLASS_RECSYN, recsyn,
461                                       global_parameters.odr_out);
462         }
463         if (elements && *elements)
464         {
465             Z_ElementSetNames *esn =
466                 odr_malloc(global_parameters.odr_out, sizeof(*esn));
467             esn->which = Z_ElementSetNames_generic;
468             esn->u.generic = odr_strdup(global_parameters.odr_out, elements);
469
470             a->u.searchRequest->smallSetElementSetNames = esn;
471             a->u.searchRequest->mediumSetElementSetNames = esn;
472         }
473         a->u.searchRequest->smallSetUpperBound = &ssub;
474         a->u.searchRequest->largeSetLowerBound = &lslb;
475         a->u.searchRequest->mediumSetPresentNumber = &mspn;
476     }
477     a->u.searchRequest->databaseNames = databaselist;
478     a->u.searchRequest->num_databaseNames = ndb;
479
480     
481     {  //scope for sending and logging queries 
482         WRBUF wbquery = wrbuf_alloc();
483         yaz_query_to_wrbuf(wbquery, a->u.searchRequest->query);
484
485
486         if (send_apdu(cl, a) >= 0)
487         {
488             client_set_state(cl, Client_Searching);
489             client_set_requestid(cl, se->requestid);
490             yaz_log(YLOG_LOG, "SearchRequest %s %s %s", 
491                     client_get_database(cl)->database->url,
492                     queryenc ? queryenc : "UTF-8",
493                     wrbuf_cstr(wbquery));
494         }
495         else {
496             client_set_state(cl, Client_Error);
497             yaz_log(YLOG_WARN, "Failed SearchRequest %s  %s %s", 
498                     client_get_database(cl)->database->url, 
499                     queryenc ? queryenc : "UTF-8",
500                     wrbuf_cstr(wbquery));
501         }
502         
503         wrbuf_destroy(wbquery);
504     }    
505
506     odr_reset(global_parameters.odr_out);
507 }
508
509 void client_init_response(struct client *cl, Z_APDU *a)
510 {
511     Z_InitResponse *r = a->u.initResponse;
512
513     yaz_log(YLOG_DEBUG, "Init response %s", cl->database->database->url);
514
515     if (*r->result)
516         cl->state = Client_Continue;
517     else
518         cl->state = Client_Failed; // FIXME need to do something to the connection
519 }
520
521
522 static void ingest_raw_records(struct client *cl, Z_Records *r)
523 {
524     Z_NamePlusRecordList *rlist;
525     Z_NamePlusRecord *npr;
526     xmlDoc *doc;
527     xmlChar *buf_out;
528     int len_out;
529     if (r->which != Z_Records_DBOSD)
530     {
531         client_show_raw_error(cl, "non-surrogate diagnostics");
532         return;
533     }
534
535     rlist = r->u.databaseOrSurDiagnostics;
536     if (rlist->num_records != 1 || !rlist->records || !rlist->records[0])
537     {
538         client_show_raw_error(cl, "no records");
539         return;
540     }
541     npr = rlist->records[0];
542     if (npr->which != Z_NamePlusRecord_databaseRecord)
543     {
544         client_show_raw_error(cl, "surrogate diagnostic");
545         return;
546     }
547
548     doc = record_to_xml(client_get_database(cl), npr->u.databaseRecord);
549     if (!doc)
550     {
551         client_show_raw_error(cl, "unable to convert record to xml");
552         return;
553     }
554
555     xmlDocDumpMemory(doc, &buf_out, &len_out);
556     xmlFreeDoc(doc);
557
558     cl->show_raw->record_handler(cl->show_raw->data,
559                                  (const char *) buf_out, len_out);
560     
561     xmlFree(buf_out);
562     xfree(cl->show_raw);
563     cl->show_raw = 0;
564 }
565
566 static void ingest_records(struct client *cl, Z_Records *r)
567 {
568 #if USE_TIMING
569     yaz_timing_t t = yaz_timing_create();
570 #endif
571     struct record *rec;
572     struct session *s = client_get_session(cl);
573     Z_NamePlusRecordList *rlist;
574     int i;
575
576     if (r->which != Z_Records_DBOSD)
577         return;
578     rlist = r->u.databaseOrSurDiagnostics;
579     for (i = 0; i < rlist->num_records; i++)
580     {
581         Z_NamePlusRecord *npr = rlist->records[i];
582
583         cl->records++;
584         if (npr->which != Z_NamePlusRecord_databaseRecord)
585         {
586             yaz_log(YLOG_WARN, 
587                     "Unexpected record type, probably diagnostic %s",
588                     cl->database->database->url);
589             continue;
590         }
591
592         rec = ingest_record(cl, npr->u.databaseRecord, cl->records);
593         if (!rec)
594             continue;
595     }
596     if (rlist->num_records)
597         session_alert_watch(s, SESSION_WATCH_SHOW);
598     if (rlist->num_records)
599         session_alert_watch(s, SESSION_WATCH_RECORD);
600
601 #if USE_TIMING
602     yaz_timing_stop(t);
603     yaz_log(YLOG_LOG, "ingest_records %6.5f %3.2f %3.2f", 
604             yaz_timing_get_real(t), yaz_timing_get_user(t),
605             yaz_timing_get_sys(t));
606     yaz_timing_destroy(&t);
607 #endif
608 }
609
610
611 void client_search_response(struct client *cl, Z_APDU *a)
612 {
613     struct session *se = cl->session;
614     Z_SearchResponse *r = a->u.searchResponse;
615
616     yaz_log(YLOG_DEBUG, "Search response %s (status=%d)", 
617             cl->database->database->url, *r->searchStatus);
618
619     if (*r->searchStatus)
620     {
621         cl->hits = *r->resultCount;
622         if (cl->hits < 0)
623         {
624             yaz_log(YLOG_WARN, "Target %s returns hit count %d",
625                     cl->database->database->url, cl->hits);
626         }
627         else
628             se->total_hits += cl->hits;
629         if (r->presentStatus && !*r->presentStatus && r->records)
630         {
631             yaz_log(YLOG_DEBUG, "Records in search response %s", 
632                     cl->database->database->url);
633             ingest_records(cl, r->records);
634         }
635         cl->state = Client_Continue;
636     }
637     else
638     {          /*"FAILED"*/
639         Z_Records *recs = r->records;
640         cl->hits = 0;
641         cl->state = Client_Error;
642         if (recs && recs->which == Z_Records_NSD)
643         {
644             WRBUF w = wrbuf_alloc();
645
646             Z_DiagRec dr, *dr_p = &dr;
647             dr.which = Z_DiagRec_defaultFormat;
648             dr.u.defaultFormat = recs->u.nonSurrogateDiagnostic;
649             
650             wrbuf_printf(w, "Search response NSD %s: ",
651                          cl->database->database->url);
652             
653             cl->diagnostic = diag_to_wrbuf(&dr_p, 1, w);
654
655             yaz_log(YLOG_WARN, "%s", wrbuf_cstr(w));
656
657             cl->state = Client_Error;
658             wrbuf_destroy(w);
659         }
660         else if (recs && recs->which == Z_Records_multipleNSD)
661         {
662             WRBUF w = wrbuf_alloc();
663
664             wrbuf_printf(w, "Search response multipleNSD %s: ",
665                          cl->database->database->url);
666             cl->diagnostic = 
667                 diag_to_wrbuf(recs->u.multipleNonSurDiagnostics->diagRecs,
668                               recs->u.multipleNonSurDiagnostics->num_diagRecs,
669                               w);
670             yaz_log(YLOG_WARN, "%s", wrbuf_cstr(w));
671             cl->state = Client_Error;
672             wrbuf_destroy(w);
673         }
674     }
675 }
676
677 void client_present_response(struct client *cl, Z_APDU *a)
678 {
679     Z_PresentResponse *r = a->u.presentResponse;
680     Z_Records *recs = r->records;
681         
682     if (recs && recs->which == Z_Records_NSD)
683     {
684         WRBUF w = wrbuf_alloc();
685         
686         Z_DiagRec dr, *dr_p = &dr;
687         dr.which = Z_DiagRec_defaultFormat;
688         dr.u.defaultFormat = recs->u.nonSurrogateDiagnostic;
689         
690         wrbuf_printf(w, "Present response NSD %s: ",
691                      cl->database->database->url);
692         
693         cl->diagnostic = diag_to_wrbuf(&dr_p, 1, w);
694         
695         yaz_log(YLOG_WARN, "%s", wrbuf_cstr(w));
696         
697         cl->state = Client_Error;
698         wrbuf_destroy(w);
699
700         client_show_raw_error(cl, "non surrogate diagnostics");
701     }
702     else if (recs && recs->which == Z_Records_multipleNSD)
703     {
704         WRBUF w = wrbuf_alloc();
705         
706         wrbuf_printf(w, "Present response multipleNSD %s: ",
707                      cl->database->database->url);
708         cl->diagnostic = 
709             diag_to_wrbuf(recs->u.multipleNonSurDiagnostics->diagRecs,
710                           recs->u.multipleNonSurDiagnostics->num_diagRecs,
711                           w);
712         yaz_log(YLOG_WARN, "%s", wrbuf_cstr(w));
713         cl->state = Client_Error;
714         wrbuf_destroy(w);
715     }
716     else if (recs && !*r->presentStatus && cl->state != Client_Error)
717     {
718         yaz_log(YLOG_DEBUG, "Good Present response %s",
719                 cl->database->database->url);
720
721         // we can mix show raw and normal show ..
722         if (cl->show_raw && cl->show_raw->active)
723         {
724             cl->show_raw->active = 0; // no longer active
725             ingest_raw_records(cl, recs);
726         }
727         else
728             ingest_records(cl, recs);
729         cl->state = Client_Continue;
730     }
731     else if (*r->presentStatus) 
732     {
733         yaz_log(YLOG_WARN, "Bad Present response %s",
734                 cl->database->database->url);
735         cl->state = Client_Error;
736         client_show_raw_error(cl, "bad present response");
737     }
738 }
739
740 void client_close_response(struct client *cl, Z_APDU *a)
741 {
742     struct connection *co = cl->connection;
743     /* Z_Close *r = a->u.close; */
744
745     yaz_log(YLOG_WARN, "Close response %s", cl->database->database->url);
746
747     cl->state = Client_Failed;
748     connection_destroy(co);
749 }
750
751 int client_is_our_response(struct client *cl)
752 {
753     struct session *se = client_get_session(cl);
754
755     if (cl && (cl->requestid == se->requestid || 
756                cl->state == Client_Initializing))
757         return 1;
758     return 0;
759 }
760
761 // Set authentication token in init if one is set for the client
762 // TODO: Extend this to handle other schemes than open (should be simple)
763 static void init_authentication(struct client *cl, Z_InitRequest *req)
764 {
765     struct session_database *sdb = client_get_database(cl);
766     const char *auth = session_setting_oneval(sdb, PZ_AUTHENTICATION);
767
768     if (*auth)
769     {
770         struct connection *co = client_get_connection(cl);
771         struct session *se = client_get_session(cl);
772         Z_IdAuthentication *idAuth = odr_malloc(global_parameters.odr_out,
773                 sizeof(*idAuth));
774         idAuth->which = Z_IdAuthentication_open;
775         idAuth->u.open = odr_strdup(global_parameters.odr_out, auth);
776         req->idAuthentication = idAuth;
777         connection_set_authentication(co, nmem_strdup(se->session_nmem, auth));
778     }
779 }
780
781 static void init_zproxy(struct client *cl, Z_InitRequest *req)
782 {
783     struct session_database *sdb = client_get_database(cl);
784     char *ztarget = sdb->database->url;
785     //char *ztarget = sdb->url;    
786     const char *zproxy = session_setting_oneval(sdb, PZ_ZPROXY);
787     
788     if (*zproxy)
789         yaz_oi_set_string_oid(&req->otherInfo,
790                               global_parameters.odr_out,
791                               yaz_oid_userinfo_proxy,
792                               1, ztarget);
793 }
794
795
796 static void client_init_request(struct client *cl)
797 {
798     Z_APDU *a = zget_APDU(global_parameters.odr_out, Z_APDU_initRequest);
799
800     a->u.initRequest->implementationId = global_parameters.implementationId;
801     a->u.initRequest->implementationName = global_parameters.implementationName;
802     a->u.initRequest->implementationVersion =
803         global_parameters.implementationVersion;
804     ODR_MASK_SET(a->u.initRequest->options, Z_Options_search);
805     ODR_MASK_SET(a->u.initRequest->options, Z_Options_present);
806     ODR_MASK_SET(a->u.initRequest->options, Z_Options_namedResultSets);
807
808     ODR_MASK_SET(a->u.initRequest->protocolVersion, Z_ProtocolVersion_1);
809     ODR_MASK_SET(a->u.initRequest->protocolVersion, Z_ProtocolVersion_2);
810     ODR_MASK_SET(a->u.initRequest->protocolVersion, Z_ProtocolVersion_3);
811
812     init_authentication(cl, a->u.initRequest);
813     init_zproxy(cl, a->u.initRequest);
814
815     if (send_apdu(cl, a) >= 0)
816         client_set_state(cl, Client_Initializing);
817     else
818         client_set_state(cl, Client_Error);
819     odr_reset(global_parameters.odr_out);
820 }
821
822 void client_continue(struct client *cl)
823 {
824     if (cl->state == Client_Connected) {
825         client_init_request(cl);
826     }
827     if (cl->state == Client_Continue || cl->state == Client_Idle)
828     {
829         struct session *se = client_get_session(cl);
830         if (cl->requestid != se->requestid && cl->pquery) {
831             // we'll have to abort this because result set is to be deleted
832             client_show_raw_cancel(cl);   
833             client_send_search(cl);
834         }
835         else if (cl->show_raw)
836         {
837             client_send_raw_present(cl);
838         }
839         else if (cl->hits > 0 && cl->records < global_parameters.toget &&
840             cl->records < cl->hits) {
841             client_send_present(cl);
842         }
843         else
844             client_set_state(cl, Client_Idle);
845     }
846 }
847
848 struct client *client_create(void)
849 {
850     struct client *r;
851     if (client_freelist)
852     {
853         r = client_freelist;
854         client_freelist = client_freelist->next;
855     }
856     else
857         r = xmalloc(sizeof(struct client));
858     r->pquery = 0;
859     r->database = 0;
860     r->connection = 0;
861     r->session = 0;
862     r->hits = 0;
863     r->records = 0;
864     r->setno = 0;
865     r->requestid = -1;
866     r->diagnostic = 0;
867     r->state = Client_Disconnected;
868     r->show_raw = 0;
869     r->next = 0;
870     return r;
871 }
872
873 void client_destroy(struct client *c)
874 {
875     struct session *se = c->session;
876     if (c == se->clients)
877         se->clients = c->next;
878     else
879     {
880         struct client *cc;
881         for (cc = se->clients; cc && cc->next != c; cc = cc->next)
882             ;
883         if (cc)
884             cc->next = c->next;
885     }
886     xfree(c->pquery);
887
888     if (c->connection)
889         connection_release(c->connection);
890     c->next = client_freelist;
891     client_freelist = c;
892 }
893
894 void client_set_connection(struct client *cl, struct connection *con)
895 {
896     cl->connection = con;
897 }
898
899 void client_disconnect(struct client *cl)
900 {
901     if (cl->state != Client_Idle)
902         cl->state = Client_Disconnected;
903     client_set_connection(cl, 0);
904 }
905
906 // Extract terms from query into null-terminated termlist
907 static void extract_terms(NMEM nmem, struct ccl_rpn_node *query, char **termlist)
908 {
909     int num = 0;
910
911     pull_terms(nmem, query, termlist, &num);
912     termlist[num] = 0;
913 }
914
915 // Initialize CCL map for a target
916 static CCL_bibset prepare_cclmap(struct client *cl)
917 {
918     struct session_database *sdb = client_get_database(cl);
919     struct setting *s;
920     CCL_bibset res;
921
922     if (!sdb->settings)
923         return 0;
924     res = ccl_qual_mk();
925     for (s = sdb->settings[PZ_CCLMAP]; s; s = s->next)
926     {
927         char *p = strchr(s->name + 3, ':');
928         if (!p)
929         {
930             yaz_log(YLOG_WARN, "Malformed cclmap name: %s", s->name);
931             ccl_qual_rm(&res);
932             return 0;
933         }
934         p++;
935         ccl_qual_fitem(res, s->value, p);
936     }
937     return res;
938 }
939
940 // Parse the query given the settings specific to this client
941 int client_parse_query(struct client *cl, const char *query)
942 {
943     struct session *se = client_get_session(cl);
944     struct ccl_rpn_node *cn;
945     int cerror, cpos;
946     CCL_bibset ccl_map = prepare_cclmap(cl);
947
948     if (!ccl_map)
949         return -1;
950
951     cn = ccl_find_str(ccl_map, query, &cerror, &cpos);
952     ccl_qual_rm(&ccl_map);
953     if (!cn)
954     {
955         cl->state = Client_Error;
956         yaz_log(YLOG_WARN, "Failed to parse query for %s",
957                          client_get_database(cl)->database->url);
958         return -1;
959     }
960     wrbuf_rewind(se->wrbuf);
961     ccl_pquery(se->wrbuf, cn);
962     xfree(cl->pquery);
963     cl->pquery = xstrdup(wrbuf_cstr(se->wrbuf));
964
965     if (!se->relevance)
966     {
967         // Initialize relevance structure with query terms
968         char *p[512];
969         extract_terms(se->nmem, cn, p);
970         se->relevance = relevance_create(
971             global_parameters.server->relevance_pct,
972             se->nmem, (const char **) p,
973             se->expected_maxrecs);
974     }
975
976     ccl_rpn_delete(cn);
977     return 0;
978 }
979
980 void client_set_session(struct client *cl, struct session *se)
981 {
982     cl->session = se;
983     cl->next = se->clients;
984     se->clients = cl;
985 }
986
987 int client_is_active(struct client *cl)
988 {
989     if (cl->connection && (cl->state == Client_Continue ||
990                            cl->state == Client_Connecting ||
991                            cl->state == Client_Connected ||
992                            cl->state == Client_Initializing ||
993                            cl->state == Client_Searching ||
994                            cl->state == Client_Presenting))
995         return 1;
996     return 0;
997 }
998
999 struct client *client_next_in_session(struct client *cl)
1000 {
1001     if (cl)
1002         return cl->next;
1003     return 0;
1004
1005 }
1006
1007 int client_get_hits(struct client *cl)
1008 {
1009     return cl->hits;
1010 }
1011
1012 int client_get_num_records(struct client *cl)
1013 {
1014     return cl->records;
1015 }
1016
1017 int client_get_diagnostic(struct client *cl)
1018 {
1019     return cl->diagnostic;
1020 }
1021
1022 void client_set_database(struct client *cl, struct session_database *db)
1023 {
1024     cl->database = db;
1025 }
1026
1027 struct host *client_get_host(struct client *cl)
1028 {
1029     return client_get_database(cl)->database->host;
1030 }
1031
1032 const char *client_get_url(struct client *cl)
1033 {
1034     return client_get_database(cl)->database->url;
1035 }
1036
1037 /*
1038  * Local variables:
1039  * c-basic-offset: 4
1040  * indent-tabs-mode: nil
1041  * End:
1042  * vim: shiftwidth=4 tabstop=8 expandtab
1043  */