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