350d8802be71d88dfaf871de85552365e0d074c0
[pazpar2-moved-to-github.git] / src / logic.c
1 /* This file is part of Pazpar2.
2    Copyright (C) 2006-2008 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 logic.c
21     \brief high-level logic; mostly user sessions and settings
22 */
23
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <sys/time.h>
28 #include <unistd.h>
29 #include <sys/socket.h>
30 #include <netdb.h>
31 #include <signal.h>
32 #include <ctype.h>
33 #include <assert.h>
34
35 #include <yaz/marcdisp.h>
36 #include <yaz/comstack.h>
37 #include <yaz/tcpip.h>
38 #include <yaz/proto.h>
39 #include <yaz/readconf.h>
40 #include <yaz/pquery.h>
41 #include <yaz/otherinfo.h>
42 #include <yaz/yaz-util.h>
43 #include <yaz/nmem.h>
44 #include <yaz/query-charset.h>
45 #include <yaz/querytowrbuf.h>
46 #include <yaz/oid_db.h>
47 #include <yaz/snprintf.h>
48
49 #if HAVE_CONFIG_H
50 #include "cconfig.h"
51 #endif
52
53 #define USE_TIMING 0
54 #if USE_TIMING
55 #include <yaz/timing.h>
56 #endif
57
58 #include <netinet/in.h>
59
60 #include "pazpar2.h"
61 #include "eventl.h"
62 #include "http.h"
63 #include "termlists.h"
64 #include "reclists.h"
65 #include "relevance.h"
66 #include "config.h"
67 #include "database.h"
68 #include "client.h"
69 #include "settings.h"
70 #include "normalize7bit.h"
71
72 #define TERMLIST_HIGH_SCORE 25
73
74 #define MAX_CHUNK 15
75
76 // Note: Some things in this structure will eventually move to configuration
77 struct parameters global_parameters = 
78 {
79     "",
80     "",
81     "", 
82     0,
83     0,   // dump_records
84     0,   // debug_mode
85     30,  // operations timeout 
86     "81",
87     "Index Data PazPar2",
88     VERSION,
89     60,   // session timeout 
90     100,
91     MAX_CHUNK,
92     0,
93     0,
94     180, // Z39.50 session timeout
95     15   // Connect timeout
96 };
97
98 // Recursively traverse query structure to extract terms.
99 void pull_terms(NMEM nmem, struct ccl_rpn_node *n, char **termlist, int *num)
100 {
101     char **words;
102     int numwords;
103     int i;
104
105     switch (n->kind)
106     {
107     case CCL_RPN_AND:
108     case CCL_RPN_OR:
109     case CCL_RPN_NOT:
110     case CCL_RPN_PROX:
111         pull_terms(nmem, n->u.p[0], termlist, num);
112         pull_terms(nmem, n->u.p[1], termlist, num);
113         break;
114     case CCL_RPN_TERM:
115         nmem_strsplit(nmem, " ", n->u.t.term, &words, &numwords);
116         for (i = 0; i < numwords; i++)
117             termlist[(*num)++] = words[i];
118         break;
119     default: // NOOP
120         break;
121     }
122 }
123
124
125
126 static void add_facet(struct session *s, const char *type, const char *value)
127 {
128     int i;
129
130     if (!*value)
131         return;
132     for (i = 0; i < s->num_termlists; i++)
133         if (!strcmp(s->termlists[i].name, type))
134             break;
135     if (i == s->num_termlists)
136     {
137         if (i == SESSION_MAX_TERMLISTS)
138         {
139             yaz_log(YLOG_FATAL, "Too many termlists");
140             return;
141         }
142
143         s->termlists[i].name = nmem_strdup(s->nmem, type);
144         s->termlists[i].termlist 
145             = termlist_create(s->nmem, s->expected_maxrecs,
146                               TERMLIST_HIGH_SCORE);
147         s->num_termlists = i + 1;
148     }
149     termlist_insert(s->termlists[i].termlist, value);
150 }
151
152 xmlDoc *record_to_xml(struct session_database *sdb, Z_External *rec)
153 {
154     struct database *db = sdb->database;
155     xmlDoc *rdoc = 0;
156     const Odr_oid *oid = rec->direct_reference;
157
158     /* convert response record to XML somehow */
159     if (rec->which == Z_External_octet && oid
160         && !oid_oidcmp(oid, yaz_oid_recsyn_xml))
161     {
162         /* xml already */
163         rdoc = xmlParseMemory((char*) rec->u.octet_aligned->buf,
164                               rec->u.octet_aligned->len);
165         if (!rdoc)
166         {
167             yaz_log(YLOG_FATAL, "Non-wellformed XML received from %s",
168                     db->url);
169             return 0;
170         }
171     }
172     else if (rec->which == Z_External_OPAC)
173     {
174         if (!sdb->yaz_marc)
175         {
176             yaz_log(YLOG_WARN, "MARC decoding not configured");
177             return 0;
178         }
179         else
180         {
181             /* OPAC gets converted to XML too */
182             WRBUF wrbuf_opac = wrbuf_alloc();
183             /* MARCXML inside the OPAC XML. Charset is in effect because we
184                use the yaz_marc handle */
185             yaz_marc_xml(sdb->yaz_marc, YAZ_MARC_MARCXML);
186             yaz_opac_decode_wrbuf(sdb->yaz_marc, rec->u.opac, wrbuf_opac);
187             
188             rdoc = xmlParseMemory((char*) wrbuf_buf(wrbuf_opac),
189                                   wrbuf_len(wrbuf_opac));
190             if (!rdoc)
191             {
192                 yaz_log(YLOG_WARN, "Unable to parse OPAC XML");
193                 /* Was used to debug bug #1348 */
194 #if 0
195                 FILE *f = fopen("/tmp/opac.xml.txt", "wb");
196                 if (f)
197                 {
198                     fwrite(wrbuf_buf(wrbuf_opac), 1, wrbuf_len(wrbuf_opac), f);
199                     fclose(f);
200                 }
201 #endif
202             }
203             wrbuf_destroy(wrbuf_opac);
204         }
205     }
206     else if (oid && yaz_oid_is_iso2709(oid))
207     {
208         /* ISO2709 gets converted to MARCXML */
209         if (!sdb->yaz_marc)
210         {
211             yaz_log(YLOG_WARN, "MARC decoding not configured");
212             return 0;
213         }
214         else
215         {
216             xmlNode *res;
217             char *buf;
218             int len;
219             
220             if (rec->which != Z_External_octet)
221             {
222                 yaz_log(YLOG_WARN, "Unexpected external branch, probably BER %s",
223                         db->url);
224                 return 0;
225             }
226             buf = (char*) rec->u.octet_aligned->buf;
227             len = rec->u.octet_aligned->len;
228             if (yaz_marc_read_iso2709(sdb->yaz_marc, buf, len) < 0)
229             {
230                 yaz_log(YLOG_WARN, "Failed to decode MARC %s", db->url);
231                 return 0;
232             }
233             
234             if (yaz_marc_write_xml(sdb->yaz_marc, &res,
235                                    "http://www.loc.gov/MARC21/slim", 0, 0) < 0)
236             {
237                 yaz_log(YLOG_WARN, "Failed to encode as XML %s",
238                         db->url);
239                 return 0;
240             }
241             rdoc = xmlNewDoc((xmlChar *) "1.0");
242             xmlDocSetRootElement(rdoc, res);
243         }
244     }
245     else
246     {
247         char oid_name_buf[OID_STR_MAX];
248         const char *oid_name = yaz_oid_to_string_buf(oid, 0, oid_name_buf);
249         yaz_log(YLOG_FATAL, 
250                 "Unable to handle record of type %s from %s", 
251                 oid_name, db->url);
252         return 0;
253     }
254     
255     if (global_parameters.dump_records)
256     {
257         FILE *lf = yaz_log_file();
258         if (lf)
259         {
260             yaz_log(YLOG_LOG, "Un-normalized record from %s", db->url);
261 #if LIBXML_VERSION >= 20600
262             xmlDocFormatDump(lf, rdoc, 1);
263 #else
264             xmlDocDump(lf, rdoc);
265 #endif
266             fprintf(lf, "\n");
267         }
268     }
269     return rdoc;
270 }
271
272 #define MAX_XSLT_ARGS 16
273
274 // Add static values from session database settings if applicable
275 static void insert_settings_parameters(struct session_database *sdb,
276                                        struct session *se, char **parms)
277 {
278     struct conf_service *service = global_parameters.server->service;
279     int i;
280     int nparms = 0;
281     int offset = 0;
282
283     for (i = 0; i < service->num_metadata; i++)
284     {
285         struct conf_metadata *md = &service->metadata[i];
286         int setting;
287
288         if (md->setting == Metadata_setting_parameter &&
289             (setting = settings_offset(md->name)) > 0)
290         {
291             const char *val = session_setting_oneval(sdb, setting);
292             if (val && nparms < MAX_XSLT_ARGS)
293             {
294                 char *buf;
295                 int len = strlen(val);
296                 buf = nmem_malloc(se->nmem, len + 3);
297                 buf[0] = '\'';
298                 strcpy(buf + 1, val);
299                 buf[len+1] = '\'';
300                 buf[len+2] = '\0';
301                 parms[offset++] = md->name;
302                 parms[offset++] = buf;
303                 nparms++;
304             }
305         }
306     }
307     parms[offset] = 0;
308 }
309
310 // Add static values from session database settings if applicable
311 static void insert_settings_values(struct session_database *sdb, xmlDoc *doc)
312 {
313     struct conf_service *service = global_parameters.server->service;
314     int i;
315
316     for (i = 0; i < service->num_metadata; i++)
317     {
318         struct conf_metadata *md = &service->metadata[i];
319         int offset;
320
321         if (md->setting == Metadata_setting_postproc &&
322             (offset = settings_offset(md->name)) > 0)
323         {
324             const char *val = session_setting_oneval(sdb, offset);
325             if (val)
326             {
327                 xmlNode *r = xmlDocGetRootElement(doc);
328                 xmlNode *n = xmlNewTextChild(r, 0, (xmlChar *) "metadata",
329                                              (xmlChar *) val);
330                 xmlSetProp(n, (xmlChar *) "type", (xmlChar *) md->name);
331             }
332         }
333     }
334 }
335
336 xmlDoc *normalize_record(struct session_database *sdb, struct session *se,
337                          Z_External *rec)
338 {
339     struct database_retrievalmap *m;
340     xmlDoc *rdoc = record_to_xml(sdb, rec);
341     if (rdoc)
342     {
343         for (m = sdb->map; m; m = m->next)
344         {
345             xmlDoc *new = 0;
346             
347             {
348                 xmlNodePtr root = 0;
349                 char *parms[MAX_XSLT_ARGS*2+1];
350
351                 insert_settings_parameters(sdb, se, parms);
352
353                 new = xsltApplyStylesheet(m->stylesheet, rdoc, (const char **) parms);
354                 root= xmlDocGetRootElement(new);
355                 if (!new || !root || !(root->children))
356                 {
357                     yaz_log(YLOG_WARN, "XSLT transformation failed from %s",
358                             sdb->database->url);
359                     xmlFreeDoc(new);
360                     xmlFreeDoc(rdoc);
361                     return 0;
362                 }
363             }
364             
365             xmlFreeDoc(rdoc);
366             rdoc = new;
367         }
368
369         insert_settings_values(sdb, rdoc);
370
371         if (global_parameters.dump_records)
372         {
373             FILE *lf = yaz_log_file();
374             
375             if (lf)
376             {
377                 yaz_log(YLOG_LOG, "Normalized record from %s", 
378                         sdb->database->url);
379 #if LIBXML_VERSION >= 20600
380                 xmlDocFormatDump(lf, rdoc, 1);
381 #else
382                 xmlDocDump(lf, rdoc);
383 #endif
384                 fprintf(lf, "\n");
385             }
386         }
387     }
388     return rdoc;
389 }
390
391 // Retrieve first defined value for 'name' for given database.
392 // Will be extended to take into account user associated with session
393 const char *session_setting_oneval(struct session_database *db, int offset)
394 {
395     if (!db->settings[offset])
396         return "";
397     return db->settings[offset]->value;
398 }
399
400
401
402 // Initialize YAZ Map structures for MARC-based targets
403 static int prepare_yazmarc(struct session_database *sdb)
404 {
405     const char *s;
406
407     if (!sdb->settings)
408     {
409         yaz_log(YLOG_WARN, "No settings for %s", sdb->database->url);
410         return -1;
411     }
412     if ((s = session_setting_oneval(sdb, PZ_NATIVESYNTAX)) 
413         && !strncmp(s, "iso2709", 7))
414     {
415         char *encoding = "marc-8s", *e;
416         yaz_iconv_t cm;
417
418         // See if a native encoding is specified
419         if ((e = strchr(s, ';')))
420             encoding = e + 1;
421
422         sdb->yaz_marc = yaz_marc_create();
423         yaz_marc_subfield_str(sdb->yaz_marc, "\t");
424         
425         cm = yaz_iconv_open("utf-8", encoding);
426         if (!cm)
427         {
428             yaz_log(YLOG_FATAL, 
429                     "Unable to map from %s to UTF-8 for target %s", 
430                     encoding, sdb->database->url);
431             return -1;
432         }
433         yaz_marc_iconv(sdb->yaz_marc, cm);
434     }
435     return 0;
436 }
437
438 // Prepare XSLT stylesheets for record normalization
439 // Structures are allocated on the session_wide nmem to avoid having
440 // to recompute this for every search. This would lead
441 // to leaking if a single session was to repeatedly change the PZ_XSLT
442 // setting. However, this is not a realistic use scenario.
443 static int prepare_map(struct session *se, struct session_database *sdb)
444 {
445     const char *s;
446
447     if (!sdb->settings)
448     {
449         yaz_log(YLOG_WARN, "No settings on %s", sdb->database->url);
450         return -1;
451     }
452     if ((s = session_setting_oneval(sdb, PZ_XSLT)))
453     {
454         char **stylesheets;
455         struct database_retrievalmap **m = &sdb->map;
456         int num, i;
457         char auto_stylesheet[256];
458
459         if (!strcmp(s, "auto"))
460         {
461             const char *request_syntax = session_setting_oneval(
462                 sdb, PZ_REQUESTSYNTAX);
463             if (request_syntax)
464             {
465                 char *cp;
466                 yaz_snprintf(auto_stylesheet, sizeof(auto_stylesheet),
467                              "%s.xsl", request_syntax);
468                 for (cp = auto_stylesheet; *cp; cp++)
469                 {
470                     /* deliberately only consider ASCII */
471                     if (*cp > 32 && *cp < 127)
472                         *cp = tolower(*cp);
473                 }
474                 s = auto_stylesheet;
475             }
476             else
477             {
478                 yaz_log(YLOG_WARN, "No pz:requestsyntax for auto stylesheet");
479             }
480         }
481         nmem_strsplit(se->session_nmem, ",", s, &stylesheets, &num);
482         for (i = 0; i < num; i++)
483         {
484             (*m) = nmem_malloc(se->session_nmem, sizeof(**m));
485             (*m)->next = 0;
486             if (!((*m)->stylesheet = conf_load_stylesheet(stylesheets[i])))
487             {
488                 yaz_log(YLOG_FATAL|YLOG_ERRNO, "Unable to load stylesheet: %s",
489                         stylesheets[i]);
490                 return -1;
491             }
492             m = &(*m)->next;
493         }
494     }
495     if (!sdb->map)
496         yaz_log(YLOG_WARN, "No Normalization stylesheet for target %s",
497                 sdb->database->url);
498     return 0;
499 }
500
501 // This analyzes settings and recomputes any supporting data structures
502 // if necessary.
503 static int prepare_session_database(struct session *se, 
504                                     struct session_database *sdb)
505 {
506     if (!sdb->settings)
507     {
508         yaz_log(YLOG_WARN, 
509                 "No settings associated with %s", sdb->database->url);
510         return -1;
511     }
512     if (sdb->settings[PZ_NATIVESYNTAX] && !sdb->yaz_marc)
513     {
514         if (prepare_yazmarc(sdb) < 0)
515             return -1;
516     }
517     if (sdb->settings[PZ_XSLT] && !sdb->map)
518     {
519         if (prepare_map(se, sdb) < 0)
520             return -1;
521     }
522     return 0;
523 }
524
525 // called if watch should be removed because http_channel is to be destroyed
526 static void session_watch_cancel(void *data, struct http_channel *c,
527                                  void *data2)
528 {
529     struct session_watchentry *ent = data;
530
531     ent->fun = 0;
532     ent->data = 0;
533     ent->obs = 0;
534 }
535
536 // set watch. Returns 0=OK, -1 if watch is already set
537 int session_set_watch(struct session *s, int what, 
538                       session_watchfun fun, void *data,
539                       struct http_channel *chan)
540 {
541     if (s->watchlist[what].fun)
542         return -1;
543     s->watchlist[what].fun = fun;
544     s->watchlist[what].data = data;
545     s->watchlist[what].obs = http_add_observer(chan, &s->watchlist[what],
546                                                session_watch_cancel);
547     return 0;
548 }
549
550 void session_alert_watch(struct session *s, int what)
551 {
552     if (s->watchlist[what].fun)
553     {
554         /* our watch is no longer associated with http_channel */
555         void *data;
556         session_watchfun fun;
557
558         http_remove_observer(s->watchlist[what].obs);
559         fun = s->watchlist[what].fun;
560         data = s->watchlist[what].data;
561
562         /* reset watch before fun is invoked - in case fun wants to set
563            it again */
564         s->watchlist[what].fun = 0;
565         s->watchlist[what].data = 0;
566         s->watchlist[what].obs = 0;
567
568         fun(data);
569     }
570 }
571
572 //callback for grep_databases
573 static void select_targets_callback(void *context, struct session_database *db)
574 {
575     struct session *se = (struct session*) context;
576     struct client *cl = client_create();
577     client_set_database(cl, db);
578     client_set_session(cl, se);
579 }
580
581 // Associates a set of clients with a session;
582 // Note: Session-databases represent databases with per-session 
583 // setting overrides
584 int select_targets(struct session *se, struct database_criterion *crit)
585 {
586     while (se->clients)
587         client_destroy(se->clients);
588
589     return session_grep_databases(se, crit, select_targets_callback);
590 }
591
592 int session_active_clients(struct session *s)
593 {
594     struct client *c;
595     int res = 0;
596
597     for (c = s->clients; c; c = client_next_in_session(c))
598         if (client_is_active(c))
599             res++;
600
601     return res;
602 }
603
604 // parses crit1=val1,crit2=val2|val3,...
605 static struct database_criterion *parse_filter(NMEM m, const char *buf)
606 {
607     struct database_criterion *res = 0;
608     char **values;
609     int num;
610     int i;
611
612     if (!buf || !*buf)
613         return 0;
614     nmem_strsplit(m, ",", buf,  &values, &num);
615     for (i = 0; i < num; i++)
616     {
617         char **subvalues;
618         int subnum;
619         int subi;
620         struct database_criterion *new = nmem_malloc(m, sizeof(*new));
621         char *eq = strchr(values[i], '=');
622         if (!eq)
623         {
624             yaz_log(YLOG_WARN, "Missing equal-sign in filter");
625             return 0;
626         }
627         *(eq++) = '\0';
628         new->name = values[i];
629         nmem_strsplit(m, "|", eq, &subvalues, &subnum);
630         new->values = 0;
631         for (subi = 0; subi < subnum; subi++)
632         {
633             struct database_criterion_value *newv
634                 = nmem_malloc(m, sizeof(*newv));
635             newv->value = subvalues[subi];
636             newv->next = new->values;
637             new->values = newv;
638         }
639         new->next = res;
640         res = new;
641     }
642     return res;
643 }
644
645 enum pazpar2_error_code search(struct session *se,
646                                char *query, char *filter,
647                                const char **addinfo)
648 {
649     int live_channels = 0;
650     int no_working = 0;
651     int no_failed = 0;
652     struct client *cl;
653     struct database_criterion *criteria;
654
655     yaz_log(YLOG_DEBUG, "Search");
656
657     *addinfo = 0;
658     nmem_reset(se->nmem);
659     se->relevance = 0;
660     se->total_records = se->total_hits = se->total_merged = 0;
661     se->reclist = 0;
662     se->num_termlists = 0;
663     criteria = parse_filter(se->nmem, filter);
664     se->requestid++;
665     live_channels = select_targets(se, criteria);
666     if (live_channels)
667     {
668         int maxrecs = live_channels * global_parameters.toget;
669         se->reclist = reclist_create(se->nmem, maxrecs);
670         se->expected_maxrecs = maxrecs;
671     }
672     else
673         return PAZPAR2_NO_TARGETS;
674
675     for (cl = se->clients; cl; cl = client_next_in_session(cl))
676     {
677         if (prepare_session_database(se, client_get_database(cl)) < 0)
678         {
679             *addinfo = client_get_database(cl)->database->url;
680             return PAZPAR2_CONFIG_TARGET;
681         }
682         // Parse query for target
683         if (client_parse_query(cl, query) < 0)
684             no_failed++;
685         else
686         {
687             no_working++;
688             client_prep_connection(cl);
689         }
690     }
691
692     // If no queries could be mapped, we signal an error
693     if (no_working == 0)
694     {
695         *addinfo = "query";
696         return PAZPAR2_MALFORMED_PARAMETER_VALUE;
697     }
698     return PAZPAR2_NO_ERROR;
699 }
700
701 // Creates a new session_database object for a database
702 static void session_init_databases_fun(void *context, struct database *db)
703 {
704     struct session *se = (struct session *) context;
705     struct session_database *new = nmem_malloc(se->session_nmem, sizeof(*new));
706     int num = settings_num();
707     int i;
708
709     new->database = db;
710     new->yaz_marc = 0;
711     
712     new->map = 0;
713     new->settings 
714         = nmem_malloc(se->session_nmem, sizeof(struct settings *) * num);
715     memset(new->settings, 0, sizeof(struct settings*) * num);
716
717     if (db->settings)
718     {
719         for (i = 0; i < num; i++)
720             new->settings[i] = db->settings[i];
721     }
722     new->next = se->databases;
723     se->databases = new;
724 }
725
726 // Doesn't free memory associated with sdb -- nmem takes care of that
727 static void session_database_destroy(struct session_database *sdb)
728 {
729     struct database_retrievalmap *m;
730
731     for (m = sdb->map; m; m = m->next)
732         xsltFreeStylesheet(m->stylesheet);
733     if (sdb->yaz_marc)
734         yaz_marc_destroy(sdb->yaz_marc);
735 }
736
737 // Initialize session_database list -- this represents this session's view
738 // of the database list -- subject to modification by the settings ws command
739 void session_init_databases(struct session *se)
740 {
741     se->databases = 0;
742     predef_grep_databases(se, 0, session_init_databases_fun);
743 }
744
745 // Probably session_init_databases_fun should be refactored instead of
746 // called here.
747 static struct session_database *load_session_database(struct session *se, 
748                                                       char *id)
749 {
750     struct database *db = find_database(id, 0);
751
752     session_init_databases_fun((void*) se, db);
753     // New sdb is head of se->databases list
754     return se->databases;
755 }
756
757 // Find an existing session database. If not found, load it
758 static struct session_database *find_session_database(struct session *se, 
759                                                       char *id)
760 {
761     struct session_database *sdb;
762
763     for (sdb = se->databases; sdb; sdb = sdb->next)
764         if (!strcmp(sdb->database->url, id))
765             return sdb;
766     return load_session_database(se, id);
767 }
768
769 // Apply a session override to a database
770 void session_apply_setting(struct session *se, char *dbname, char *setting,
771                            char *value)
772 {
773     struct session_database *sdb = find_session_database(se, dbname);
774     struct setting *new = nmem_malloc(se->session_nmem, sizeof(*new));
775     int offset = settings_offset_cprefix(setting);
776
777     if (offset < 0)
778     {
779         yaz_log(YLOG_WARN, "Unknown setting %s", setting);
780         return;
781     }
782     // Jakub: This breaks the filter setting.
783     /*if (offset == PZ_ID)
784       {
785       yaz_log(YLOG_WARN, "No need to set pz:id setting. Ignoring");
786       return;
787       }*/
788     new->precedence = 0;
789     new->target = dbname;
790     new->name = setting;
791     new->value = value;
792     new->next = sdb->settings[offset];
793     sdb->settings[offset] = new;
794
795     // Force later recompute of settings-driven data structures
796     // (happens when a search starts and client connections are prepared)
797     switch (offset)
798     {
799     case PZ_NATIVESYNTAX:
800         if (sdb->yaz_marc)
801         {
802             yaz_marc_destroy(sdb->yaz_marc);
803             sdb->yaz_marc = 0;
804         }
805         break;
806     case PZ_XSLT:
807         if (sdb->map)
808         {
809             struct database_retrievalmap *m;
810             // We don't worry about the map structure -- it's in nmem
811             for (m = sdb->map; m; m = m->next)
812                 xsltFreeStylesheet(m->stylesheet);
813             sdb->map = 0;
814         }
815         break;
816     }
817 }
818
819 void destroy_session(struct session *s)
820 {
821     struct session_database *sdb;
822
823     while (s->clients)
824         client_destroy(s->clients);
825     for (sdb = s->databases; sdb; sdb = sdb->next)
826         session_database_destroy(sdb);
827     nmem_destroy(s->nmem);
828     wrbuf_destroy(s->wrbuf);
829 }
830
831 struct session *new_session(NMEM nmem) 
832 {
833     int i;
834     struct session *session = nmem_malloc(nmem, sizeof(*session));
835
836     yaz_log(YLOG_DEBUG, "New Pazpar2 session");
837     
838     session->relevance = 0;
839     session->total_hits = 0;
840     session->total_records = 0;
841     session->num_termlists = 0;
842     session->reclist = 0;
843     session->requestid = -1;
844     session->clients = 0;
845     session->expected_maxrecs = 0;
846     session->session_nmem = nmem;
847     session->nmem = nmem_create();
848     session->wrbuf = wrbuf_alloc();
849     session->databases = 0;
850     for (i = 0; i <= SESSION_WATCH_MAX; i++)
851     {
852         session->watchlist[i].data = 0;
853         session->watchlist[i].fun = 0;
854     }
855     return session;
856 }
857
858 struct hitsbytarget *hitsbytarget(struct session *se, int *count, NMEM nmem)
859 {
860     struct hitsbytarget *res = 0;
861     struct client *cl;
862     size_t sz = 0;
863
864     for (cl = se->clients; cl; cl = client_next_in_session(cl))
865         sz++;
866
867     res = nmem_malloc(nmem, sizeof(*res) * sz);
868     *count = 0;
869     for (cl = se->clients; cl; cl = client_next_in_session(cl))
870     {
871         const char *name = session_setting_oneval(client_get_database(cl),
872                                                   PZ_NAME);
873
874         res[*count].id = client_get_database(cl)->database->url;
875         res[*count].name = *name ? name : "Unknown";
876         res[*count].hits = client_get_hits(cl);
877         res[*count].records = client_get_num_records(cl);
878         res[*count].diagnostic = client_get_diagnostic(cl);
879         res[*count].state = client_get_state_str(cl);
880         res[*count].connected  = client_get_connection(cl) ? 1 : 0;
881         (*count)++;
882     }
883     return res;
884 }
885
886 struct termlist_score **termlist(struct session *s, const char *name, int *num)
887 {
888     int i;
889
890     for (i = 0; i < s->num_termlists; i++)
891         if (!strcmp((const char *) s->termlists[i].name, name))
892             return termlist_highscore(s->termlists[i].termlist, num);
893     return 0;
894 }
895
896 #ifdef MISSING_HEADERS
897 void report_nmem_stats(void)
898 {
899     size_t in_use, is_free;
900
901     nmem_get_memory_in_use(&in_use);
902     nmem_get_memory_free(&is_free);
903
904     yaz_log(YLOG_LOG, "nmem stat: use=%ld free=%ld", 
905             (long) in_use, (long) is_free);
906 }
907 #endif
908
909 struct record_cluster *show_single(struct session *s, const char *id)
910 {
911     struct record_cluster *r;
912
913     reclist_rewind(s->reclist);
914     while ((r = reclist_read_record(s->reclist)))
915         if (!strcmp(r->recid, id))
916             return r;
917     return 0;
918 }
919
920 struct record_cluster **show(struct session *s, struct reclist_sortparms *sp, 
921                              int start, int *num, int *total, int *sumhits, 
922                              NMEM nmem_show)
923 {
924     struct record_cluster **recs = nmem_malloc(nmem_show, *num 
925                                                * sizeof(struct record_cluster *));
926     struct reclist_sortparms *spp;
927     int i;
928 #if USE_TIMING    
929     yaz_timing_t t = yaz_timing_create();
930 #endif
931
932     if (!s->relevance)
933     {
934         *num = 0;
935         *total = 0;
936         *sumhits = 0;
937         recs = 0;
938     }
939     else
940     {
941         for (spp = sp; spp; spp = spp->next)
942             if (spp->type == Metadata_sortkey_relevance)
943             {
944                 relevance_prepare_read(s->relevance, s->reclist);
945                 break;
946             }
947         reclist_sort(s->reclist, sp);
948         
949         *total = s->reclist->num_records;
950         *sumhits = s->total_hits;
951         
952         for (i = 0; i < start; i++)
953             if (!reclist_read_record(s->reclist))
954             {
955                 *num = 0;
956                 recs = 0;
957                 break;
958             }
959         
960         for (i = 0; i < *num; i++)
961         {
962             struct record_cluster *r = reclist_read_record(s->reclist);
963             if (!r)
964             {
965                 *num = i;
966                 break;
967             }
968             recs[i] = r;
969         }
970     }
971 #if USE_TIMING
972     yaz_timing_stop(t);
973     yaz_log(YLOG_LOG, "show %6.5f %3.2f %3.2f", 
974             yaz_timing_get_real(t), yaz_timing_get_user(t),
975             yaz_timing_get_sys(t));
976     yaz_timing_destroy(&t);
977 #endif
978     return recs;
979 }
980
981 void statistics(struct session *se, struct statistics *stat)
982 {
983     struct client *cl;
984     int count = 0;
985
986     memset(stat, 0, sizeof(*stat));
987     for (cl = se->clients; cl; cl = client_next_in_session(cl))
988     {
989         if (!client_get_connection(cl))
990             stat->num_no_connection++;
991         switch (client_get_state(cl))
992         {
993         case Client_Connecting: stat->num_connecting++; break;
994         case Client_Initializing: stat->num_initializing++; break;
995         case Client_Searching: stat->num_searching++; break;
996         case Client_Presenting: stat->num_presenting++; break;
997         case Client_Idle: stat->num_idle++; break;
998         case Client_Failed: stat->num_failed++; break;
999         case Client_Error: stat->num_error++; break;
1000         default: break;
1001         }
1002         count++;
1003     }
1004     stat->num_hits = se->total_hits;
1005     stat->num_records = se->total_records;
1006
1007     stat->num_clients = count;
1008 }
1009
1010 void start_http_listener(void)
1011 {
1012     char hp[128] = "";
1013     struct conf_server *ser = global_parameters.server;
1014
1015     if (*global_parameters.listener_override)
1016         strcpy(hp, global_parameters.listener_override);
1017     else
1018     {
1019         strcpy(hp, ser->host ? ser->host : "");
1020         if (ser->port)
1021         {
1022             if (*hp)
1023                 strcat(hp, ":");
1024             sprintf(hp + strlen(hp), "%d", ser->port);
1025         }
1026     }
1027     http_init(hp);
1028 }
1029
1030 void start_proxy(void)
1031 {
1032     char hp[128] = "";
1033     struct conf_server *ser = global_parameters.server;
1034
1035     if (*global_parameters.proxy_override)
1036         strcpy(hp, global_parameters.proxy_override);
1037     else if (ser->proxy_host || ser->proxy_port)
1038     {
1039         strcpy(hp, ser->proxy_host ? ser->proxy_host : "");
1040         if (ser->proxy_port)
1041         {
1042             if (*hp)
1043                 strcat(hp, ":");
1044             sprintf(hp + strlen(hp), "%d", ser->proxy_port);
1045         }
1046     }
1047     else
1048         return;
1049
1050     http_set_proxyaddr(hp, ser->myurl ? ser->myurl : "");
1051 }
1052
1053
1054 // Master list of connections we're handling events to
1055 static IOCHAN channel_list = 0; 
1056 void pazpar2_add_channel(IOCHAN chan)
1057 {
1058     chan->next = channel_list;
1059     channel_list = chan;
1060 }
1061
1062 void pazpar2_event_loop()
1063 {
1064     event_loop(&channel_list);
1065 }
1066
1067 static struct record_metadata *record_metadata_init(
1068     NMEM nmem, char *value, enum conf_metadata_type type)
1069 {
1070     struct record_metadata *rec_md = record_metadata_create(nmem);
1071     if (type == Metadata_type_generic)
1072     {
1073         char * p = value;
1074         p = normalize7bit_generic(p, " ,/.:([");
1075         
1076         rec_md->data.text.disp = nmem_strdup(nmem, p);
1077         rec_md->data.text.sort = 0;
1078     }
1079     else if (type == Metadata_type_year || type == Metadata_type_date)
1080     {
1081         int first, last;
1082         int longdate = 0;
1083
1084         if (type == Metadata_type_date)
1085             longdate = 1;
1086         if (extract7bit_dates((char *) value, &first, &last, longdate) < 0)
1087             return 0;
1088
1089         rec_md->data.number.min = first;
1090         rec_md->data.number.max = last;
1091     }
1092     else
1093         return 0;
1094     return rec_md;
1095 }
1096
1097 struct record *ingest_record(struct client *cl, Z_External *rec,
1098                              int record_no)
1099 {
1100     xmlDoc *xdoc = normalize_record(client_get_database(cl),
1101                                     client_get_session(cl), rec);
1102     xmlNode *root, *n;
1103     struct record *record;
1104     struct record_cluster *cluster;
1105     struct session *se = client_get_session(cl);
1106     xmlChar *mergekey, *mergekey_norm;
1107     xmlChar *type = 0;
1108     xmlChar *value = 0;
1109     struct conf_service *service = global_parameters.server->service;
1110     const char *norm_str = 0;
1111     pp2_relevance_token_t prt = 0;
1112     WRBUF norm_wr = 0;
1113
1114     if (!xdoc)
1115         return 0;
1116
1117     root = xmlDocGetRootElement(xdoc);
1118     if (!(mergekey = xmlGetProp(root, (xmlChar *) "mergekey")))
1119     {
1120         yaz_log(YLOG_WARN, "No mergekey found in record");
1121         xmlFreeDoc(xdoc);
1122         return 0;
1123     }
1124     
1125     record = record_create(se->nmem, 
1126                            service->num_metadata, service->num_sortkeys, cl,
1127                            record_no);
1128
1129     prt = pp2_relevance_tokenize(
1130         global_parameters.server->mergekey_pct, (const char *) mergekey);
1131
1132
1133     norm_wr = wrbuf_alloc();
1134     
1135     while ((norm_str = pp2_relevance_token_next(prt)))
1136     {
1137         if (*norm_str)
1138         {
1139             if (wrbuf_len(norm_wr))
1140                 wrbuf_puts(norm_wr, " ");
1141             wrbuf_puts(norm_wr, norm_str);
1142         }
1143     }
1144         
1145     mergekey_norm = (xmlChar *)nmem_strdup(se->nmem, wrbuf_cstr(norm_wr));
1146     wrbuf_destroy(norm_wr);
1147
1148     pp2_relevance_token_destroy(prt);
1149
1150     xmlFree(mergekey);
1151     
1152     cluster = reclist_insert(se->reclist, 
1153                              global_parameters.server->service, 
1154                              record, (char *) mergekey_norm, 
1155                              &se->total_merged);
1156     if (global_parameters.dump_records)
1157         yaz_log(YLOG_LOG, "Cluster id %s from %s (#%d)", cluster->recid,
1158                 client_get_database(cl)->database->url, record_no);
1159     if (!cluster)
1160     {
1161         /* no room for record */
1162         xmlFreeDoc(xdoc);
1163         return 0;
1164     }
1165     relevance_newrec(se->relevance, cluster);
1166      
1167      
1168      // now parsing XML record and adding data to cluster or record metadata
1169      for (n = root->children; n; n = n->next)
1170      {
1171          if (type)
1172              xmlFree(type);
1173          if (value)
1174              xmlFree(value);
1175          type = value = 0;
1176
1177          if (n->type != XML_ELEMENT_NODE)
1178              continue;
1179          if (!strcmp((const char *) n->name, "metadata"))
1180          {
1181              struct conf_metadata *ser_md = 0;
1182              struct conf_sortkey *ser_sk = 0;
1183              struct record_metadata **wheretoput = 0;
1184              struct record_metadata *rec_md = 0;
1185              int md_field_id = -1;
1186              int sk_field_id = -1;
1187
1188              type = xmlGetProp(n, (xmlChar *) "type");
1189              value = xmlNodeListGetString(xdoc, n->children, 1);
1190
1191              if (!type || !value || !*value)
1192                  continue;
1193
1194              md_field_id 
1195                  = conf_service_metadata_field_id(service, (const char *) type);
1196              if (md_field_id < 0)
1197              {
1198                  yaz_log(YLOG_WARN, 
1199                          "Ignoring unknown metadata element: %s", type);
1200                  continue;
1201              }
1202
1203              ser_md = &service->metadata[md_field_id];
1204
1205              if (ser_md->sortkey_offset >= 0){
1206                  sk_field_id = ser_md->sortkey_offset;
1207                  ser_sk = &service->sortkeys[sk_field_id];
1208              }
1209
1210              // non-merged metadata
1211              rec_md = record_metadata_init(se->nmem, (char *) value,
1212                                            ser_md->type);
1213              if (!rec_md)
1214              {
1215                  yaz_log(YLOG_WARN, "bad metadata data '%s' for element '%s'",
1216                          value, type);
1217                  continue;
1218              }
1219              rec_md->next = record->metadata[md_field_id];
1220              record->metadata[md_field_id] = rec_md;
1221
1222              // merged metadata
1223              rec_md = record_metadata_init(se->nmem, (char *) value,
1224                                            ser_md->type);
1225              wheretoput = &cluster->metadata[md_field_id];
1226
1227              // and polulate with data:
1228              // assign cluster or record based on merge action
1229              if (ser_md->merge == Metadata_merge_unique)
1230              {
1231                  struct record_metadata *mnode;
1232                  for (mnode = *wheretoput; mnode; mnode = mnode->next)
1233                      if (!strcmp((const char *) mnode->data.text.disp, 
1234                                  rec_md->data.text.disp))
1235                          break;
1236                  if (!mnode)
1237                  {
1238                      rec_md->next = *wheretoput;
1239                      *wheretoput = rec_md;
1240                  }
1241              }
1242              else if (ser_md->merge == Metadata_merge_longest)
1243              {
1244                  if (!*wheretoput 
1245                      || strlen(rec_md->data.text.disp) 
1246                      > strlen((*wheretoput)->data.text.disp))
1247                  {
1248                      *wheretoput = rec_md;
1249                      if (ser_sk)
1250                      {
1251                          const char *sort_str = 0;
1252                          int skip_article = 
1253                              ser_sk->type == Metadata_sortkey_skiparticle;
1254
1255                          if (!cluster->sortkeys[sk_field_id])
1256                              cluster->sortkeys[sk_field_id] = 
1257                                  nmem_malloc(se->nmem, 
1258                                              sizeof(union data_types));
1259                          
1260                          prt = pp2_relevance_tokenize(
1261                              global_parameters.server->sort_pct,
1262                              rec_md->data.text.disp);
1263
1264                          pp2_relevance_token_next(prt);
1265                          
1266                          sort_str = pp2_get_sort(prt, skip_article);
1267                          
1268                          cluster->sortkeys[sk_field_id]->text.disp = 
1269                              rec_md->data.text.disp;
1270                          if (!sort_str)
1271                          {
1272                              sort_str = rec_md->data.text.disp;
1273                              yaz_log(YLOG_WARN, 
1274                                      "Could not make sortkey. Bug #1858");
1275                          }
1276                          cluster->sortkeys[sk_field_id]->text.sort = 
1277                              nmem_strdup(se->nmem, sort_str);
1278 #if 0
1279                          yaz_log(YLOG_LOG, "text disp=%s",
1280                                  cluster->sortkeys[sk_field_id]->text.disp);
1281                          yaz_log(YLOG_LOG, "text sort=%s",
1282                                  cluster->sortkeys[sk_field_id]->text.sort);
1283 #endif
1284                          pp2_relevance_token_destroy(prt);
1285                     }
1286                 }
1287             }
1288             else if (ser_md->merge == Metadata_merge_all)
1289             {
1290                 rec_md->next = *wheretoput;
1291                 *wheretoput = rec_md;
1292             }
1293             else if (ser_md->merge == Metadata_merge_range)
1294             {
1295                 if (!*wheretoput)
1296                 {
1297                     *wheretoput = rec_md;
1298                     if (ser_sk)
1299                         cluster->sortkeys[sk_field_id] 
1300                             = &rec_md->data;
1301                 }
1302                 else
1303                 {
1304                     int this_min = rec_md->data.number.min;
1305                     int this_max = rec_md->data.number.max;
1306                     if (this_min < (*wheretoput)->data.number.min)
1307                         (*wheretoput)->data.number.min = this_min;
1308                     if (this_max > (*wheretoput)->data.number.max)
1309                         (*wheretoput)->data.number.max = this_max;
1310                 }
1311             }
1312
1313
1314             // ranking of _all_ fields enabled ... 
1315             if (ser_md->rank)
1316                 relevance_countwords(se->relevance, cluster, 
1317                                      (char *) value, ser_md->rank);
1318
1319             // construct facets ... 
1320             if (ser_md->termlist)
1321             {
1322                 if (ser_md->type == Metadata_type_year)
1323                 {
1324                     char year[64];
1325                     sprintf(year, "%d", rec_md->data.number.max);
1326                     add_facet(se, (char *) type, year);
1327                     if (rec_md->data.number.max != rec_md->data.number.min)
1328                     {
1329                         sprintf(year, "%d", rec_md->data.number.min);
1330                         add_facet(se, (char *) type, year);
1331                     }
1332                 }
1333                 else
1334                     add_facet(se, (char *) type, (char *) value);
1335             }
1336
1337             // cleaning up
1338             xmlFree(type);
1339             xmlFree(value);
1340             type = value = 0;
1341         }
1342         else
1343             yaz_log(YLOG_WARN,
1344                     "Unexpected element %s in internal record", n->name);
1345     }
1346     if (type)
1347         xmlFree(type);
1348     if (value)
1349         xmlFree(value);
1350
1351     xmlFreeDoc(xdoc);
1352
1353     relevance_donerecord(se->relevance, cluster);
1354     se->total_records++;
1355
1356     return record;
1357 }
1358
1359
1360
1361 /*
1362  * Local variables:
1363  * c-basic-offset: 4
1364  * indent-tabs-mode: nil
1365  * End:
1366  * vim: shiftwidth=4 tabstop=8 expandtab
1367  */