Merge branch 'master' of ssh://git.indexdata.com:222/home/git/pub/pazpar2
[pazpar2-moved-to-github.git] / src / client.c
1 /* This file is part of Pazpar2.
2    Copyright (C) 2006-2011 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 #include <stdlib.h>
28 #include <stdio.h>
29 #include <string.h>
30 #if HAVE_SYS_TIME_H
31 #include <sys/time.h>
32 #endif
33 #if HAVE_UNISTD_H
34 #include <unistd.h>
35 #endif
36 #include <signal.h>
37 #include <assert.h>
38
39 #include <yaz/marcdisp.h>
40 #include <yaz/comstack.h>
41 #include <yaz/tcpip.h>
42 #include <yaz/proto.h>
43 #include <yaz/readconf.h>
44 #include <yaz/pquery.h>
45 #include <yaz/otherinfo.h>
46 #include <yaz/yaz-util.h>
47 #include <yaz/nmem.h>
48 #include <yaz/query-charset.h>
49 #include <yaz/querytowrbuf.h>
50 #include <yaz/oid_db.h>
51 #include <yaz/diagbib1.h>
52 #include <yaz/snprintf.h>
53 #include <yaz/rpn2cql.h>
54 #include <yaz/rpn2solr.h>
55
56 #define USE_TIMING 0
57 #if USE_TIMING
58 #include <yaz/timing.h>
59 #endif
60
61 #include "ppmutex.h"
62 #include "session.h"
63 #include "parameters.h"
64 #include "client.h"
65 #include "connection.h"
66 #include "settings.h"
67 #include "relevance.h"
68 #include "incref.h"
69
70 static YAZ_MUTEX g_mutex = 0;
71 static int no_clients = 0;
72
73 static int client_use(int delta)
74 {
75     int clients;
76     if (!g_mutex)
77         yaz_mutex_create(&g_mutex);
78     yaz_mutex_enter(g_mutex);
79     no_clients += delta;
80     clients = no_clients;
81     yaz_mutex_leave(g_mutex);
82     yaz_log(YLOG_DEBUG, "%s clients=%d", delta == 0 ? "" : (delta > 0 ? "INC" : "DEC"), clients);
83     return clients;
84 }
85
86 int  clients_count(void) {
87     return client_use(0);
88 }
89
90
91 /** \brief Represents client state for a connection to one search target */
92 struct client {
93     struct session_database *database;
94     struct connection *connection;
95     struct session *session;
96     char *pquery; // Current search
97     char *cqlquery; // used for SRU targets only
98     Odr_int hits;
99     int record_offset;
100     int maxrecs;
101     int startrecs;
102     int diagnostic;
103     int preferred;
104     enum client_state state;
105     struct show_raw *show_raw;
106     ZOOM_resultset resultset;
107     YAZ_MUTEX mutex;
108     int ref_count;
109     /* copy of database->url */
110     char *url;
111 };
112
113 struct show_raw {
114     int active; // whether this request has been sent to the server
115     int position;
116     int binary;
117     char *syntax;
118     char *esn;
119     void (*error_handler)(void *data, const char *addinfo);
120     void (*record_handler)(void *data, const char *buf, size_t sz);
121     void *data;
122     struct show_raw *next;
123 };
124
125 static const char *client_states[] = {
126     "Client_Connecting",
127     "Client_Idle",
128     "Client_Working",
129     "Client_Error",
130     "Client_Failed",
131     "Client_Disconnected"
132 };
133
134 const char *client_get_state_str(struct client *cl)
135 {
136     return client_states[cl->state];
137 }
138
139 enum client_state client_get_state(struct client *cl)
140 {
141     return cl->state;
142 }
143
144 void client_set_state(struct client *cl, enum client_state st)
145 {
146     int was_active = 0;
147     if (client_is_active(cl))
148         was_active = 1;
149     cl->state = st;
150     /* If client is going from being active to inactive and all clients
151        are now idle we fire a watch for the session . The assumption is
152        that session is not mutex locked if client is already active */
153     if (was_active && !client_is_active(cl) && cl->session)
154     {
155
156         int no_active = session_active_clients(cl->session);
157         yaz_log(YLOG_DEBUG, "%s: releasing watches on zero active: %d", client_get_url(cl), no_active);
158         if (no_active == 0) {
159             session_alert_watch(cl->session, SESSION_WATCH_SHOW);
160             session_alert_watch(cl->session, SESSION_WATCH_SHOW_PREF);
161         }
162     }
163 }
164
165 static void client_show_raw_error(struct client *cl, const char *addinfo);
166
167 struct connection *client_get_connection(struct client *cl)
168 {
169     return cl->connection;
170 }
171
172 struct session_database *client_get_database(struct client *cl)
173 {
174     return cl->database;
175 }
176
177 struct session *client_get_session(struct client *cl)
178 {
179     return cl->session;
180 }
181
182 const char *client_get_pquery(struct client *cl)
183 {
184     return cl->pquery;
185 }
186
187 static void client_send_raw_present(struct client *cl);
188 static int nativesyntax_to_type(struct session_database *sdb, char *type,
189                                 ZOOM_record rec);
190
191 static void client_show_immediate(
192     ZOOM_resultset resultset, struct session_database *sdb, int position,
193     void *data,
194     void (*error_handler)(void *data, const char *addinfo),
195     void (*record_handler)(void *data, const char *buf, size_t sz),
196     int binary)
197 {
198     ZOOM_record rec = 0;
199     char type[80];
200     const char *buf;
201     int len;
202
203     if (!resultset)
204     {
205         error_handler(data, "no resultset");
206         return;
207     }
208     rec = ZOOM_resultset_record(resultset, position-1);
209     if (!rec)
210     {
211         error_handler(data, "no record");
212         return;
213     }
214     if (binary)
215         strcpy(type, "raw");
216     else
217         nativesyntax_to_type(sdb, type, rec);
218     buf = ZOOM_record_get(rec, type, &len);
219     if (!buf)
220     {
221         error_handler(data, "no record");
222         return;
223     }
224     record_handler(data, buf, len);
225 }
226
227
228 int client_show_raw_begin(struct client *cl, int position,
229                           const char *syntax, const char *esn,
230                           void *data,
231                           void (*error_handler)(void *data, const char *addinfo),
232                           void (*record_handler)(void *data, const char *buf,
233                                                  size_t sz),
234                           int binary)
235 {
236     if (syntax == 0 && esn == 0)
237         client_show_immediate(cl->resultset, client_get_database(cl),
238                               position, data,
239                               error_handler, record_handler,
240                               binary);
241     else
242     {
243         struct show_raw *rr, **rrp;
244
245         if (!cl->connection)
246             return -1;
247     
248
249         rr = xmalloc(sizeof(*rr));
250         rr->position = position;
251         rr->active = 0;
252         rr->data = data;
253         rr->error_handler = error_handler;
254         rr->record_handler = record_handler;
255         rr->binary = binary;
256         if (syntax)
257             rr->syntax = xstrdup(syntax);
258         else
259             rr->syntax = 0;
260         if (esn)
261             rr->esn = xstrdup(esn);
262         else
263             rr->esn = 0;
264         rr->next = 0;
265         
266         for (rrp = &cl->show_raw; *rrp; rrp = &(*rrp)->next)
267             ;
268         *rrp = rr;
269         
270         if (cl->state == Client_Failed)
271         {
272             client_show_raw_error(cl, "client failed");
273         }
274         else if (cl->state == Client_Disconnected)
275         {
276             client_show_raw_error(cl, "client disconnected");
277         }
278         else
279         {
280             client_send_raw_present(cl);
281         }
282     }
283     return 0;
284 }
285
286 static void client_show_raw_delete(struct show_raw *r)
287 {
288     xfree(r->syntax);
289     xfree(r->esn);
290     xfree(r);
291 }
292
293 void client_show_raw_remove(struct client *cl, void *data)
294 {
295     struct show_raw *rr = data;
296     struct show_raw **rrp = &cl->show_raw;
297     while (*rrp != rr)
298         rrp = &(*rrp)->next;
299     if (*rrp)
300     {
301         *rrp = rr->next;
302         client_show_raw_delete(rr);
303     }
304 }
305
306 void client_show_raw_dequeue(struct client *cl)
307 {
308     struct show_raw *rr = cl->show_raw;
309
310     cl->show_raw = rr->next;
311     client_show_raw_delete(rr);
312 }
313
314 static void client_show_raw_error(struct client *cl, const char *addinfo)
315 {
316     while (cl->show_raw)
317     {
318         cl->show_raw->error_handler(cl->show_raw->data, addinfo);
319         client_show_raw_dequeue(cl);
320     }
321 }
322
323 static void client_send_raw_present(struct client *cl)
324 {
325     struct session_database *sdb = client_get_database(cl);
326     struct connection *co = client_get_connection(cl);
327     ZOOM_resultset set = cl->resultset;
328
329     int offset = cl->show_raw->position;
330     const char *syntax = 0;
331     const char *elements = 0;
332
333     assert(cl->show_raw);
334     assert(set);
335
336     yaz_log(YLOG_DEBUG, "%s: trying to present %d record(s) from %d",
337             client_get_url(cl), 1, offset);
338
339     if (cl->show_raw->syntax)
340         syntax = cl->show_raw->syntax;
341     else
342         syntax = session_setting_oneval(sdb, PZ_REQUESTSYNTAX);
343     ZOOM_resultset_option_set(set, "preferredRecordSyntax", syntax);
344
345     if (cl->show_raw->esn)
346         elements = cl->show_raw->esn;
347     else
348         elements = session_setting_oneval(sdb, PZ_ELEMENTS);
349     if (elements && *elements)
350         ZOOM_resultset_option_set(set, "elementSetName", elements);
351
352     ZOOM_resultset_records(set, 0, offset-1, 1);
353     cl->show_raw->active = 1;
354
355     connection_continue(co);
356 }
357
358 static int nativesyntax_to_type(struct session_database *sdb, char *type,
359                                 ZOOM_record rec)
360 {
361     const char *s = session_setting_oneval(sdb, PZ_NATIVESYNTAX);
362
363     if (s && *s)
364     {
365         if (!strncmp(s, "iso2709", 7))
366         {
367             const char *cp = strchr(s, ';');
368             yaz_snprintf(type, 80, "xml; charset=%s", cp ? cp+1 : "marc-8s");
369         }
370         else if (!strncmp(s, "xml", 3))
371         {
372             strcpy(type, "xml");
373         }
374         else if (!strncmp(s, "txml", 4))
375         {
376             const char *cp = strchr(s, ';');
377             yaz_snprintf(type, 80, "txml; charset=%s", cp ? cp+1 : "marc-8s");
378         }
379         else
380             return -1;
381         return 0;
382     }
383     else  /* attempt to deduce structure */
384     {
385         const char *syntax = ZOOM_record_get(rec, "syntax", NULL);
386         if (syntax)
387         {
388             if (!strcmp(syntax, "XML"))
389             {
390                 strcpy(type, "xml");
391                 return 0;
392             }
393             else if (!strcmp(syntax, "TXML"))
394                 {
395                     strcpy(type, "txml");
396                     return 0;
397                 }
398             else if (!strcmp(syntax, "USmarc") || !strcmp(syntax, "MARC21"))
399             {
400                 strcpy(type, "xml; charset=marc8-s");
401                 return 0;
402             }
403             else return -1;
404         }
405         else return -1;
406     }
407 }
408
409 /**
410  * TODO Consider thread safety!!!
411  *
412  */
413 int client_report_facets(struct client *cl, ZOOM_resultset rs) {
414     int facet_idx;
415     ZOOM_facet_field *facets = ZOOM_resultset_facets(rs);
416     int facet_num;
417     struct session *se = client_get_session(cl);
418     facet_num = ZOOM_resultset_facets_size(rs);
419     yaz_log(YLOG_DEBUG, "client_report_facets: %d", facet_num);
420
421     for (facet_idx = 0; facet_idx < facet_num; facet_idx++) {
422         const char *name = ZOOM_facet_field_name(facets[facet_idx]);
423         size_t term_idx;
424         size_t term_num = ZOOM_facet_field_term_count(facets[facet_idx]);
425         for (term_idx = 0; term_idx < term_num; term_idx++ ) {
426             int freq;
427             const char *term = ZOOM_facet_field_get_term(facets[facet_idx], term_idx, &freq);
428             if (term)
429                 add_facet(se, name, term, freq);
430         }
431     }
432
433     return 0;
434 }
435
436 static void ingest_raw_record(struct client *cl, ZOOM_record rec)
437 {
438     const char *buf;
439     int len;
440     char type[80];
441
442     if (cl->show_raw->binary)
443         strcpy(type, "raw");
444     else
445     {
446         struct session_database *sdb = client_get_database(cl);
447         nativesyntax_to_type(sdb, type, rec);
448     }
449
450     buf = ZOOM_record_get(rec, type, &len);
451     cl->show_raw->record_handler(cl->show_raw->data,  buf, len);
452     client_show_raw_dequeue(cl);
453 }
454
455 void client_check_preferred_watch(struct client *cl)
456 {
457     struct session *se = cl->session;
458     yaz_log(YLOG_DEBUG, "client_check_preferred_watch: %s ", client_get_url(cl));
459     if (se)
460     {
461         client_unlock(cl);
462         if (session_is_preferred_clients_ready(se)) {
463             session_alert_watch(se, SESSION_WATCH_SHOW_PREF);
464         }
465         else
466             yaz_log(YLOG_DEBUG, "client_check_preferred_watch: Still locked on preferred targets.");
467
468         client_lock(cl);
469     }
470     else
471         yaz_log(YLOG_WARN, "client_check_preferred_watch: %s. No session!", client_get_url(cl));
472
473 }
474
475 void client_search_response(struct client *cl)
476 {
477     struct connection *co = cl->connection;
478     struct session *se = cl->session;
479     ZOOM_connection link = connection_get_link(co);
480     ZOOM_resultset resultset = cl->resultset;
481
482     const char *error, *addinfo = 0;
483     
484     if (ZOOM_connection_error(link, &error, &addinfo))
485     {
486         cl->hits = 0;
487         client_set_state(cl, Client_Error);
488         yaz_log(YLOG_WARN, "Search error %s (%s): %s",
489                 error, addinfo, client_get_url(cl));
490     }
491     else
492     {
493         yaz_log(YLOG_DEBUG, "client_search_response: hits "
494                 ODR_INT_PRINTF, cl->hits);
495         client_report_facets(cl, resultset);
496         cl->record_offset = cl->startrecs;
497         cl->hits = ZOOM_resultset_size(resultset);
498         if (se) {
499             se->total_hits += cl->hits;
500             yaz_log(YLOG_DEBUG, "client_search_response: total hits "
501                     ODR_INT_PRINTF, se->total_hits);
502         }
503     }
504 }
505
506 void client_got_records(struct client *cl)
507 {
508     struct session *se = cl->session;
509     if (se)
510     {
511         client_unlock(cl);
512         session_alert_watch(se, SESSION_WATCH_SHOW);
513         session_alert_watch(se, SESSION_WATCH_RECORD);
514         client_lock(cl);
515     }
516 }
517
518 void client_record_response(struct client *cl)
519 {
520     struct connection *co = cl->connection;
521     ZOOM_connection link = connection_get_link(co);
522     ZOOM_resultset resultset = cl->resultset;
523     const char *error, *addinfo;
524
525     if (ZOOM_connection_error(link, &error, &addinfo))
526     {
527         client_set_state(cl, Client_Error);
528         yaz_log(YLOG_WARN, "Search error %s (%s): %s",
529             error, addinfo, client_get_url(cl));
530     }
531     else
532     {
533         ZOOM_record rec = 0;
534         const char *msg, *addinfo;
535         
536         if (cl->show_raw && cl->show_raw->active)
537         {
538             if ((rec = ZOOM_resultset_record(resultset,
539                                              cl->show_raw->position-1)))
540             {
541                 cl->show_raw->active = 0;
542                 ingest_raw_record(cl, rec);
543             }
544             else
545             {
546                 yaz_log(YLOG_WARN, "Expected record, but got NULL, offset=%d",
547                         cl->show_raw->position-1);
548             }
549         }
550         else
551         {
552             int offset = cl->record_offset;
553             if ((rec = ZOOM_resultset_record(resultset, offset)))
554             {
555                 cl->record_offset++;
556                 if (cl->session == 0)
557                     ;
558                 else if (ZOOM_record_error(rec, &msg, &addinfo, 0))
559                 {
560                     yaz_log(YLOG_WARN, "Record error %s (%s): %s (rec #%d)",
561                             msg, addinfo, client_get_url(cl),
562                             cl->record_offset);
563                 }
564                 else
565                 {
566                     struct session_database *sdb = client_get_database(cl);
567                     NMEM nmem = nmem_create();
568                     const char *xmlrec;
569                     char type[80];
570
571                     if (nativesyntax_to_type(sdb, type, rec))
572                         yaz_log(YLOG_WARN, "Failed to determine record type");
573                     xmlrec = ZOOM_record_get(rec, type, NULL);
574                     if (!xmlrec)
575                         yaz_log(YLOG_WARN, "ZOOM_record_get failed from %s",
576                                 client_get_url(cl));
577                     else
578                     {
579                         if (ingest_record(cl, xmlrec, cl->record_offset, nmem))
580                             yaz_log(YLOG_WARN, "Failed to ingest from %s",
581                                     client_get_url(cl));
582                     }
583                     nmem_destroy(nmem);
584                 }
585             }
586             else
587             {
588                 yaz_log(YLOG_WARN, "Expected record, but got NULL, offset=%d",
589                         offset);
590             }
591         }
592     }
593 }
594
595 static int client_set_facets_request(struct client *cl, ZOOM_connection link)
596 {
597     struct session_database *sdb = client_get_database(cl);
598     const char *opt_facet_term_sort  = session_setting_oneval(sdb, PZ_TERMLIST_TERM_SORT);
599     const char *opt_facet_term_count = session_setting_oneval(sdb, PZ_TERMLIST_TERM_COUNT);
600
601     /* Future record filtering on target */
602     /* const char *opt_facet_record_filter = session_setting_oneval(sdb, PZ_RECORDFILTER); */
603
604     /* Disable when no count is set */
605     /* TODO Verify: Do we need to reset the  ZOOM facets if a ZOOM Connection is being reused??? */
606     if (opt_facet_term_count && *opt_facet_term_count)
607     {
608         int index = 0;
609         struct session *session = client_get_session(cl);
610         struct conf_service *service = session->service;
611         int num = service->num_metadata;
612         WRBUF wrbuf = wrbuf_alloc();
613         yaz_log(YLOG_DEBUG, "Facet settings, sort: %s count: %s",
614                 opt_facet_term_sort, opt_facet_term_count);
615         for (index = 0; index < num; index++)
616         {
617             struct conf_metadata *conf_meta = &service->metadata[index];
618             if (conf_meta->termlist)
619             {
620                 if (wrbuf_len(wrbuf))
621                     wrbuf_puts(wrbuf, ", ");
622                 wrbuf_printf(wrbuf, "@attr 1=%s", conf_meta->name);
623                 
624                 if (opt_facet_term_sort && *opt_facet_term_sort)
625                     wrbuf_printf(wrbuf, " @attr 2=%s", opt_facet_term_sort);
626                 wrbuf_printf(wrbuf, " @attr 3=%s", opt_facet_term_count);
627             }
628         }
629         if (wrbuf_len(wrbuf))
630         {
631             yaz_log(YLOG_LOG, "Setting ZOOM facets option: %s", wrbuf_cstr(wrbuf));
632             ZOOM_connection_option_set(link, "facets", wrbuf_cstr(wrbuf));
633             return 1;
634         }
635     }
636     return 0;
637 }
638
639 int client_has_facet(struct client *cl, const char *name) {
640     ZOOM_facet_field facet_field;
641     if (!cl || !cl->resultset || !name) {
642         return 0;
643     }
644     facet_field = ZOOM_resultset_get_facet_field(cl->resultset, name);
645     if (facet_field) {
646         return 1;
647     }
648     return 0;
649 }
650
651
652 void client_start_search(struct client *cl)
653 {
654     struct session_database *sdb = client_get_database(cl);
655     struct connection *co = client_get_connection(cl);
656     ZOOM_connection link = connection_get_link(co);
657     ZOOM_resultset rs;
658     char *databaseName = sdb->database->databases[0];
659     const char *opt_piggyback   = session_setting_oneval(sdb, PZ_PIGGYBACK);
660     const char *opt_queryenc    = session_setting_oneval(sdb, PZ_QUERYENCODING);
661     const char *opt_elements    = session_setting_oneval(sdb, PZ_ELEMENTS);
662     const char *opt_requestsyn  = session_setting_oneval(sdb, PZ_REQUESTSYNTAX);
663     const char *opt_maxrecs     = session_setting_oneval(sdb, PZ_MAXRECS);
664     const char *opt_sru         = session_setting_oneval(sdb, PZ_SRU);
665     const char *opt_sort        = session_setting_oneval(sdb, PZ_SORT);
666     const char *opt_preferred   = session_setting_oneval(sdb, PZ_PREFERRED);
667     const char *extra_args      = session_setting_oneval(sdb, PZ_EXTRA_ARGS);
668     char maxrecs_str[24], startrecs_str[24];
669
670     assert(link);
671
672     cl->hits = -1;
673     cl->record_offset = 0;
674     cl->diagnostic = 0;
675
676     if (extra_args && *extra_args)
677         ZOOM_connection_option_set(link, "extraArgs", extra_args);
678
679     if (opt_preferred) {
680         cl->preferred = atoi(opt_preferred);
681         if (cl->preferred)
682             yaz_log(YLOG_LOG, "Target %s has preferred status: %d", sdb->database->url, cl->preferred);
683     }
684     client_set_state(cl, Client_Working);
685
686     if (*opt_piggyback)
687         ZOOM_connection_option_set(link, "piggyback", opt_piggyback);
688     else
689         ZOOM_connection_option_set(link, "piggyback", "1");
690     if (*opt_queryenc)
691         ZOOM_connection_option_set(link, "rpnCharset", opt_queryenc);
692     if (*opt_sru && *opt_elements)
693         ZOOM_connection_option_set(link, "schema", opt_elements);
694     else if (*opt_elements)
695         ZOOM_connection_option_set(link, "elementSetName", opt_elements);
696     if (*opt_requestsyn)
697         ZOOM_connection_option_set(link, "preferredRecordSyntax", opt_requestsyn);
698
699     if (opt_maxrecs && *opt_maxrecs)
700     {
701         cl->maxrecs = atoi(opt_maxrecs);
702     }
703
704     /* convert back to string representation used in ZOOM API */
705     sprintf(maxrecs_str, "%d", cl->maxrecs);
706     ZOOM_connection_option_set(link, "count", maxrecs_str);
707
708     if (cl->maxrecs > 20)
709         ZOOM_connection_option_set(link, "presentChunk", "20");
710     else
711         ZOOM_connection_option_set(link, "presentChunk", maxrecs_str);
712
713     sprintf(startrecs_str, "%d", cl->startrecs);
714     ZOOM_connection_option_set(link, "start", startrecs_str);
715
716     if (databaseName)
717         ZOOM_connection_option_set(link, "databaseName", databaseName);
718
719     /* TODO Verify does it break something for CQL targets(non-SOLR) ? */
720     /* facets definition is in PQF */
721     client_set_facets_request(cl, link);
722
723     if (cl->cqlquery)
724     {
725         ZOOM_query q = ZOOM_query_create();
726         yaz_log(YLOG_LOG, "Search %s CQL: %s", sdb->database->url, cl->cqlquery);
727         ZOOM_query_cql(q, cl->cqlquery);
728         if (*opt_sort)
729             ZOOM_query_sortby(q, opt_sort);
730         rs = ZOOM_connection_search(link, q);
731         ZOOM_query_destroy(q);
732     }
733     else
734     {
735         yaz_log(YLOG_LOG, "Search %s PQF: %s", sdb->database->url, cl->pquery);
736         rs = ZOOM_connection_search_pqf(link, cl->pquery);
737     }
738     ZOOM_resultset_destroy(cl->resultset);
739     cl->resultset = rs;
740     connection_continue(co);
741 }
742
743 struct client *client_create(void)
744 {
745     struct client *cl = xmalloc(sizeof(*cl));
746     cl->maxrecs = 100;
747     cl->startrecs = 0;
748     cl->pquery = 0;
749     cl->cqlquery = 0;
750     cl->database = 0;
751     cl->connection = 0;
752     cl->session = 0;
753     cl->hits = 0;
754     cl->record_offset = 0;
755     cl->diagnostic = 0;
756     cl->state = Client_Disconnected;
757     cl->show_raw = 0;
758     cl->resultset = 0;
759     cl->mutex = 0;
760     pazpar2_mutex_create(&cl->mutex, "client");
761     cl->preferred = 0;
762     cl->ref_count = 1;
763     cl->url = 0;
764     client_use(1);
765     
766     return cl;
767 }
768
769 void client_lock(struct client *c)
770 {
771     yaz_mutex_enter(c->mutex);
772 }
773
774 void client_unlock(struct client *c)
775 {
776     yaz_mutex_leave(c->mutex);
777 }
778
779 void client_incref(struct client *c)
780 {
781     pazpar2_incref(&c->ref_count, c->mutex);
782     yaz_log(YLOG_DEBUG, "client_incref c=%p %s cnt=%d",
783             c, client_get_url(c), c->ref_count);
784 }
785
786 int client_destroy(struct client *c)
787 {
788     if (c)
789     {
790         yaz_log(YLOG_DEBUG, "client_destroy c=%p %s cnt=%d",
791                 c, client_get_url(c), c->ref_count);
792         if (!pazpar2_decref(&c->ref_count, c->mutex))
793         {
794             xfree(c->pquery);
795             c->pquery = 0;
796             xfree(c->cqlquery);
797             c->cqlquery = 0;
798             xfree(c->url);
799             assert(!c->connection);
800
801             if (c->resultset)
802             {
803                 ZOOM_resultset_destroy(c->resultset);
804             }
805             yaz_mutex_destroy(&c->mutex);
806             xfree(c);
807             client_use(-1);
808             return 1;
809         }
810     }
811     return 0;
812 }
813
814 void client_set_connection(struct client *cl, struct connection *con)
815 {
816     if (cl->resultset)
817         ZOOM_resultset_release(cl->resultset);
818     if (con)
819     {
820         assert(cl->connection == 0);
821         cl->connection = con;
822         client_incref(cl);
823     }
824     else
825     {
826         cl->connection = con;
827         client_destroy(cl);
828     }
829 }
830
831 void client_disconnect(struct client *cl)
832 {
833     if (cl->state != Client_Idle)
834         client_set_state(cl, Client_Disconnected);
835     client_set_connection(cl, 0);
836 }
837
838 // Extract terms from query into null-terminated termlist
839 static void extract_terms(NMEM nmem, struct ccl_rpn_node *query, char **termlist)
840 {
841     int num = 0;
842
843     pull_terms(nmem, query, termlist, &num);
844     termlist[num] = 0;
845 }
846
847 // Initialize CCL map for a target
848 static CCL_bibset prepare_cclmap(struct client *cl)
849 {
850     struct session_database *sdb = client_get_database(cl);
851     struct setting *s;
852     CCL_bibset res;
853
854     if (!sdb->settings)
855         return 0;
856     res = ccl_qual_mk();
857     for (s = sdb->settings[PZ_CCLMAP]; s; s = s->next)
858     {
859         char *p = strchr(s->name + 3, ':');
860         if (!p)
861         {
862             yaz_log(YLOG_WARN, "Malformed cclmap name: %s", s->name);
863             ccl_qual_rm(&res);
864             return 0;
865         }
866         p++;
867         ccl_qual_fitem(res, s->value, p);
868     }
869     return res;
870 }
871
872 // returns a xmalloced CQL query corresponding to the pquery in client
873 static char *make_cqlquery(struct client *cl)
874 {
875     cql_transform_t cqlt = cql_transform_create();
876     Z_RPNQuery *zquery;
877     char *r;
878     WRBUF wrb = wrbuf_alloc();
879     int status;
880     ODR odr_out = odr_createmem(ODR_ENCODE);
881
882     zquery = p_query_rpn(odr_out, cl->pquery);
883     yaz_log(YLOG_LOG, "PQF: %s", cl->pquery);
884     if ((status = cql_transform_rpn2cql_wrbuf(cqlt, wrb, zquery)))
885     {
886         yaz_log(YLOG_WARN, "Failed to generate CQL query, code=%d", status);
887         r = 0;
888     }
889     else
890     {
891         r = xstrdup(wrbuf_cstr(wrb));
892     }     
893     wrbuf_destroy(wrb);
894     odr_destroy(odr_out);
895     cql_transform_close(cqlt);
896     return r;
897 }
898
899 // returns a xmalloced SOLR query corresponding to the pquery in client
900 // TODO Could prob. be merge with the similar make_cqlquery
901 static char *make_solrquery(struct client *cl)
902 {
903     solr_transform_t sqlt = solr_transform_create();
904     Z_RPNQuery *zquery;
905     char *r;
906     WRBUF wrb = wrbuf_alloc();
907     int status;
908     ODR odr_out = odr_createmem(ODR_ENCODE);
909
910     zquery = p_query_rpn(odr_out, cl->pquery);
911     yaz_log(YLOG_LOG, "PQF: %s", cl->pquery);
912     if ((status = solr_transform_rpn2solr_wrbuf(sqlt, wrb, zquery)))
913     {
914         yaz_log(YLOG_WARN, "Failed to generate SOLR query, code=%d", status);
915         r = 0;
916     }
917     else
918     {
919         r = xstrdup(wrbuf_cstr(wrb));
920     }
921     wrbuf_destroy(wrb);
922     odr_destroy(odr_out);
923     solr_transform_close(sqlt);
924     return r;
925 }
926
927 // Parse the query given the settings specific to this client
928 int client_parse_query(struct client *cl, const char *query)
929 {
930     struct session *se = client_get_session(cl);
931     struct session_database *sdb = client_get_database(cl);
932     struct ccl_rpn_node *cn;
933     int cerror, cpos;
934     CCL_bibset ccl_map = prepare_cclmap(cl);
935     const char *sru = session_setting_oneval(sdb, PZ_SRU);
936     const char *pqf_prefix = session_setting_oneval(sdb, PZ_PQF_PREFIX);
937     const char *pqf_strftime = session_setting_oneval(sdb, PZ_PQF_STRFTIME);
938     const char *query_syntax = session_setting_oneval(sdb, PZ_QUERY_SYNTAX);
939     if (!ccl_map)
940         return -1;
941
942     cn = ccl_find_str(ccl_map, query, &cerror, &cpos);
943     ccl_qual_rm(&ccl_map);
944     if (!cn)
945     {
946         client_set_state(cl, Client_Error);
947         session_log(se, YLOG_WARN, "Failed to parse CCL query '%s' for %s",
948                 query,
949                 client_get_database(cl)->database->url);
950         return -1;
951     }
952     wrbuf_rewind(se->wrbuf);
953     if (*pqf_prefix)
954     {
955         wrbuf_puts(se->wrbuf, pqf_prefix);
956         wrbuf_puts(se->wrbuf, " ");
957     }
958     if (!pqf_strftime || !*pqf_strftime)
959         ccl_pquery(se->wrbuf, cn);
960     else
961     {
962         time_t cur_time = time(0);
963         struct tm *tm =  localtime(&cur_time);
964         char tmp_str[300];
965         const char *cp = tmp_str;
966
967         /* see man strftime(3) for things .. In particular %% gets converted
968          to %.. And That's our original query .. */
969         strftime(tmp_str, sizeof(tmp_str)-1, pqf_strftime, tm);
970         for (; *cp; cp++)
971         {
972             if (cp[0] == '%')
973                 ccl_pquery(se->wrbuf, cn);
974             else
975                 wrbuf_putc(se->wrbuf, cp[0]);
976         }
977     }
978     xfree(cl->pquery);
979     cl->pquery = xstrdup(wrbuf_cstr(se->wrbuf));
980
981     xfree(cl->cqlquery);
982
983     /* Support for PQF on SRU targets.
984      * TODO Refactor */
985     yaz_log(YLOG_DEBUG, "Query syntax: %s", query_syntax);
986     if (strcmp(query_syntax, "pqf") != 0 && *sru)
987     {
988         if (!strcmp(sru, "solr")) {
989             if (!(cl->cqlquery = make_solrquery(cl)))
990                 return -1;
991         }
992         else {
993             if (!(cl->cqlquery = make_cqlquery(cl)))
994                 return -1;
995         }
996     }
997     else
998         cl->cqlquery = 0;
999
1000     /* TODO FIX Not thread safe */
1001     if (!se->relevance)
1002     {
1003         // Initialize relevance structure with query terms
1004         char *p[512];
1005         extract_terms(se->nmem, cn, p);
1006         se->relevance = relevance_create(
1007             se->service->relevance_pct,
1008             se->nmem, (const char **) p);
1009     }
1010
1011     ccl_rpn_delete(cn);
1012     return 0;
1013 }
1014
1015 void client_set_session(struct client *cl, struct session *se)
1016 {
1017     cl->session = se;
1018 }
1019
1020 int client_is_active(struct client *cl)
1021 {
1022     if (cl->connection && (cl->state == Client_Connecting ||
1023                            cl->state == Client_Working))
1024         return 1;
1025     return 0;
1026 }
1027
1028 int client_is_active_preferred(struct client *cl)
1029 {
1030     /* only count if this is a preferred target. */
1031     if (!cl->preferred)
1032         return 0;
1033     /* TODO No sure this the condition that Seb wants */
1034     if (cl->connection && (cl->state == Client_Connecting ||
1035                            cl->state == Client_Working))
1036         return 1;
1037     return 0;
1038 }
1039
1040
1041 Odr_int client_get_hits(struct client *cl)
1042 {
1043     return cl->hits;
1044 }
1045
1046 int client_get_num_records(struct client *cl)
1047 {
1048     return cl->record_offset;
1049 }
1050
1051 void client_set_diagnostic(struct client *cl, int diagnostic)
1052 {
1053     cl->diagnostic = diagnostic;
1054 }
1055
1056 int client_get_diagnostic(struct client *cl)
1057 {
1058     return cl->diagnostic;
1059 }
1060
1061 void client_set_database(struct client *cl, struct session_database *db)
1062 {
1063     cl->database = db;
1064     /* Copy the URL for safe logging even after session is gone */
1065     if (db) {
1066         cl->url = xstrdup(db->database->url);
1067     }
1068 }
1069
1070 struct host *client_get_host(struct client *cl)
1071 {
1072     return client_get_database(cl)->database->host;
1073 }
1074
1075 const char *client_get_url(struct client *cl)
1076 {
1077     if (cl->url)
1078         return cl->url;
1079     else
1080         /* This must not happen anymore, as the url is present until destruction of client  */
1081         return "NOURL";
1082 }
1083
1084 void client_set_maxrecs(struct client *cl, int v)
1085 {
1086     cl->maxrecs = v;
1087 }
1088
1089 int client_get_maxrecs(struct client *cl)
1090 {
1091     return cl->maxrecs;
1092 }
1093
1094 void client_set_startrecs(struct client *cl, int v)
1095 {
1096     cl->startrecs = v;
1097 }
1098
1099 void client_set_preferred(struct client *cl, int v)
1100 {
1101     cl->preferred = v;
1102 }
1103
1104
1105 /*
1106  * Local variables:
1107  * c-basic-offset: 4
1108  * c-file-style: "Stroustrup"
1109  * indent-tabs-mode: nil
1110  * End:
1111  * vim: shiftwidth=4 tabstop=8 expandtab
1112  */
1113