Merge branch 'master' into paz-927
[pazpar2-moved-to-github.git] / src / pazpar2_config.c
1 /* This file is part of Pazpar2.
2    Copyright (C) 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 #if HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include <string.h>
25 #include <assert.h>
26
27 #include <libxml/parser.h>
28 #include <libxml/tree.h>
29 #include <libxml/xinclude.h>
30
31 #include <yaz/yaz-util.h>
32 #include <yaz/nmem.h>
33 #include <yaz/snprintf.h>
34 #include <yaz/tpath.h>
35 #include <yaz/xml_include.h>
36
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #if HAVE_UNISTD_H
40 #include <unistd.h>
41 #endif
42 #include "ppmutex.h"
43 #include "incref.h"
44 #include "pazpar2_config.h"
45 #include "service_xslt.h"
46 #include "settings.h"
47 #include "eventl.h"
48 #include "http.h"
49
50 struct conf_config
51 {
52     NMEM nmem; /* for conf_config and servers memory */
53     struct conf_server *servers;
54
55     int no_threads;
56     WRBUF confdir;
57     char *path;
58     iochan_man_t iochan_man;
59 };
60
61 struct service_xslt
62 {
63     char *id;
64     xsltStylesheetPtr xsp;
65     struct service_xslt *next;
66 };
67
68 static char *xml_context(const xmlNode *ptr, char *res, size_t len)
69 {
70     xmlAttr *attr = ptr->properties;
71     size_t off = len - 1;
72
73     res[off] = '\0';
74     for (; attr; attr = attr->next)
75     {
76         size_t l = strlen((const char *) attr->name);
77         if (off <= l + 1)
78             break;
79         off = off - l;
80         memcpy(res + off, attr->name, l);
81         res[--off] = '@';
82     }
83     while (ptr && ptr->type == XML_ELEMENT_NODE)
84     {
85         size_t l = strlen((const char *) ptr->name);
86         if (off <= l + 1)
87             break;
88
89         off = off - l;
90         memcpy(res + off, ptr->name, l);
91         res[--off] = '/';
92
93         ptr = ptr->parent;
94     }
95     return res + off;
96 }
97
98 struct conf_service *service_init(struct conf_server *server,
99                                          int num_metadata, int num_sortkeys,
100                                          const char *service_id)
101 {
102     struct conf_service * service = 0;
103     NMEM nmem = nmem_create();
104
105
106     service = nmem_malloc(nmem, sizeof(struct conf_service));
107     service->mutex = 0;
108     service->ref_count = 1;
109     service->nmem = nmem;
110     service->next = 0;
111     service->databases = 0;
112     service->xslt_list = 0;
113     service->ccl_bibset = 0;
114     service->server = server;
115     service->session_timeout = 60; /* default session timeout */
116     service->z3950_session_timeout = 180;
117     service->z3950_operation_timeout = 30;
118     service->rank_cluster = 1;
119     service->rank_debug = 0;
120     service->rank_follow = 0.0;
121     service->rank_lead = 0.0;
122     service->rank_length = 2;
123
124     service->charsets = 0;
125
126     service->id = service_id ? nmem_strdup(nmem, service_id) : 0;
127
128     // Setup a dictionary from server.
129     service->dictionary = 0;
130
131     service->settings = nmem_malloc(nmem, sizeof(*service->settings));
132     service->settings->num_settings = PZ_MAX_EOF;
133     service->settings->settings = nmem_malloc(nmem, sizeof(struct setting*) * service->settings->num_settings);
134     memset(service->settings->settings, 0, sizeof(struct setting*) * service->settings->num_settings);
135     //  inherit_server_settings_values(service);
136
137     service->next = 0;
138
139     service->num_metadata = num_metadata;
140
141     service->metadata = 0;
142     if (service->num_metadata)
143         service->metadata
144             = nmem_malloc(nmem,
145                           sizeof(struct conf_metadata) * service->num_metadata);
146     service->num_sortkeys = num_sortkeys;
147
148     service->default_sort = nmem_strdup(nmem, "relevance");
149     service->sortkeys = 0;
150     if (service->num_sortkeys)
151         service->sortkeys
152             = nmem_malloc(nmem,
153                           sizeof(struct conf_sortkey) * service->num_sortkeys);
154     service->xml_node = 0;
155     return service;
156 }
157
158 static struct conf_metadata* conf_service_add_metadata(
159     struct conf_service *service,
160     int field_id,
161     const char *name,
162     enum conf_metadata_type type,
163     enum conf_metadata_merge merge,
164     enum conf_setting_type setting,
165     int brief,
166     int termlist,
167     const char *rank,
168     int sortkey_offset,
169     enum conf_metadata_mergekey mt,
170     const char *facetrule,
171     const char *limitmap,
172     const char *limitcluster
173     )
174 {
175     struct conf_metadata * md = 0;
176     NMEM nmem = service->nmem;
177
178     if (!service->metadata || !service->num_metadata
179         || field_id < 0  || !(field_id < service->num_metadata))
180         return 0;
181
182     md = service->metadata + field_id;
183     assert(nmem && md && name);
184
185     md->name = nmem_strdup(nmem, name);
186
187     md->type = type;
188
189     // enforcing that type_year is always range_merge
190     if (md->type == Metadata_type_year)
191         md->merge = Metadata_merge_range;
192     else
193         md->merge = merge;
194
195     md->setting = setting;
196     md->brief = brief;
197     md->termlist = termlist;
198     md->rank = nmem_strdup_null(nmem, rank);
199     md->sortkey_offset = sortkey_offset;
200     md->mergekey = mt;
201     md->facetrule = nmem_strdup_null(nmem, facetrule);
202     md->limitmap = nmem_strdup_null(nmem, limitmap);
203     md->limitcluster = nmem_strdup_null(nmem, limitcluster);
204     return md;
205 }
206
207 static struct conf_sortkey *conf_service_add_sortkey(
208     struct conf_service *service,
209     int field_id,
210     const char *name,
211     enum conf_metadata_type type)
212 {
213     struct conf_sortkey *sk = 0;
214     NMEM nmem = service->nmem;
215
216     if (!service->sortkeys || !service->num_sortkeys
217         || field_id < 0 || !(field_id < service->num_sortkeys))
218         return 0;
219
220     sk = service->sortkeys + field_id;
221
222     assert(nmem && sk && name);
223
224     sk->name = nmem_strdup(nmem, name);
225     sk->type = type;
226     return sk;
227 }
228
229 int conf_service_metadata_field_id(struct conf_service *service,
230                                    const char * name)
231 {
232     int i = 0;
233
234     if (!service || !service->metadata || !service->num_metadata)
235         return -1;
236
237     for (i = 0; i < service->num_metadata; i++)
238         if (!strcmp(name, (service->metadata[i]).name))
239             return i;
240     return -1;
241 }
242
243 int conf_service_sortkey_field_id(struct conf_service *service,
244                                   const char * name)
245 {
246     int i = 0;
247
248     if (!service || !service->sortkeys || !service->num_sortkeys)
249         return -1;
250
251     for (i = 0; i < service->num_sortkeys; i++)
252         if (!strcmp(name, (service->sortkeys[i]).name))
253             return i;
254     return -1;
255 }
256
257 static void conf_dir_path(struct conf_config *config, WRBUF w, const char *src)
258 {
259     char full_path[1024];
260     if (yaz_filepath_resolve(src, config->path,
261                              wrbuf_len(config->confdir) > 0 ?
262                              wrbuf_cstr(config->confdir) : ".",
263                              full_path))
264     {
265         wrbuf_puts(w, full_path);
266     }
267     else
268     {
269         yaz_log(YLOG_WARN, "File not found: fname=%s path=%s base=%s", src,
270                 config->path, wrbuf_cstr(config->confdir));
271         wrbuf_puts(w, src);
272     }
273 }
274
275 void service_destroy(struct conf_service *service)
276 {
277     if (service)
278     {
279         if (!pazpar2_decref(&service->ref_count, service->mutex))
280         {
281             service_xslt_destroy(service);
282             pp2_charset_fact_destroy(service->charsets);
283             ccl_qual_rm(&service->ccl_bibset);
284             yaz_mutex_destroy(&service->mutex);
285             nmem_destroy(service->nmem);
286         }
287     }
288 }
289
290 void service_incref(struct conf_service *service)
291 {
292     pazpar2_incref(&service->ref_count, service->mutex);
293 }
294
295 static int parse_metadata(struct conf_service *service, xmlNode *n,
296                           int *md_node, int *sk_node)
297 {
298     enum conf_metadata_type type = Metadata_type_generic;
299     enum conf_metadata_merge merge = Metadata_merge_no;
300     enum conf_setting_type setting = Metadata_setting_no;
301     enum conf_metadata_mergekey mergekey_type = Metadata_mergekey_no;
302     int brief = 0;
303     int termlist = 0;
304     int sortkey_offset = 0;
305     xmlChar *xml_name = 0;
306     xmlChar *xml_brief = 0;
307     xmlChar *xml_sortkey = 0;
308     xmlChar *xml_merge = 0;
309     xmlChar *xml_type = 0;
310     xmlChar *xml_termlist = 0;
311     xmlChar *xml_rank = 0;
312     xmlChar *xml_setting = 0;
313     xmlChar *xml_mergekey = 0;
314     xmlChar *xml_limitmap = 0;
315     xmlChar *xml_limitcluster = 0;
316     xmlChar *xml_icu_chain = 0;
317
318     struct _xmlAttr *attr;
319
320     assert(service);
321
322     for (attr = n->properties; attr; attr = attr->next)
323     {
324         if (!xmlStrcmp(attr->name, BAD_CAST "name") &&
325             attr->children && attr->children->type == XML_TEXT_NODE)
326             xml_name = attr->children->content;
327         else if (!xmlStrcmp(attr->name, BAD_CAST "brief") &&
328                  attr->children && attr->children->type == XML_TEXT_NODE)
329             xml_brief = attr->children->content;
330         else if (!xmlStrcmp(attr->name, BAD_CAST "sortkey") &&
331                  attr->children && attr->children->type == XML_TEXT_NODE)
332             xml_sortkey = attr->children->content;
333         else if (!xmlStrcmp(attr->name, BAD_CAST "merge") &&
334                  attr->children && attr->children->type == XML_TEXT_NODE)
335             xml_merge = attr->children->content;
336         else if (!xmlStrcmp(attr->name, BAD_CAST "type") &&
337                  attr->children && attr->children->type == XML_TEXT_NODE)
338             xml_type = attr->children->content;
339         else if (!xmlStrcmp(attr->name, BAD_CAST "termlist") &&
340                  attr->children && attr->children->type == XML_TEXT_NODE)
341             xml_termlist = attr->children->content;
342         else if (!xmlStrcmp(attr->name, BAD_CAST "rank") &&
343                  attr->children && attr->children->type == XML_TEXT_NODE)
344             xml_rank = attr->children->content;
345         else if (!xmlStrcmp(attr->name, BAD_CAST "setting") &&
346                  attr->children && attr->children->type == XML_TEXT_NODE)
347             xml_setting = attr->children->content;
348         else if (!xmlStrcmp(attr->name, BAD_CAST "mergekey") &&
349                  attr->children && attr->children->type == XML_TEXT_NODE)
350             xml_mergekey = attr->children->content;
351         else if (!xmlStrcmp(attr->name, BAD_CAST "facetrule") &&
352                  attr->children && attr->children->type == XML_TEXT_NODE)
353             xml_icu_chain = attr->children->content;
354         else if (!xmlStrcmp(attr->name, BAD_CAST "limitmap") &&
355                  attr->children && attr->children->type == XML_TEXT_NODE)
356             xml_limitmap = attr->children->content;
357         else if (!xmlStrcmp(attr->name, BAD_CAST "limitcluster") &&
358                  attr->children && attr->children->type == XML_TEXT_NODE)
359             xml_limitcluster = attr->children->content;
360         else
361         {
362             yaz_log(YLOG_FATAL, "Unknown metadata attribute '%s'", attr->name);
363             return -1;
364         }
365     }
366
367     // now do the parsing logic
368     if (!xml_name)
369     {
370         yaz_log(YLOG_FATAL, "Must specify name in metadata element");
371         return -1;
372     }
373     if (xml_brief)
374     {
375         if (!strcmp((const char *) xml_brief, "yes"))
376             brief = 1;
377         else if (strcmp((const char *) xml_brief, "no"))
378         {
379             yaz_log(YLOG_FATAL, "metadata/brief must be yes or no");
380             return -1;
381         }
382     }
383
384     if (xml_termlist)
385     {
386         if (!strcmp((const char *) xml_termlist, "yes"))
387             termlist = 1;
388         else if (strcmp((const char *) xml_termlist, "no"))
389         {
390             yaz_log(YLOG_FATAL, "metadata/termlist must be yes or no");
391             return -1;
392         }
393     }
394
395     if (xml_type)
396     {
397         if (!strcmp((const char *) xml_type, "generic"))
398             type = Metadata_type_generic;
399         else if (!strcmp((const char *) xml_type, "year"))
400             type = Metadata_type_year;
401         else if (!strcmp((const char *) xml_type, "date"))
402             type = Metadata_type_date;
403         else if (!strcmp((const char *) xml_type, "float"))
404             type = Metadata_type_float;
405         else
406         {
407             yaz_log(YLOG_FATAL,
408                     "Unknown value for metadata/type: %s", xml_type);
409             return -1;
410         }
411     }
412
413     if (xml_merge)
414     {
415         if (!strcmp((const char *) xml_merge, "no"))
416             merge = Metadata_merge_no;
417         else if (!strcmp((const char *) xml_merge, "unique"))
418             merge = Metadata_merge_unique;
419         else if (!strcmp((const char *) xml_merge, "longest"))
420             merge = Metadata_merge_longest;
421         else if (!strcmp((const char *) xml_merge, "range"))
422             merge = Metadata_merge_range;
423         else if (!strcmp((const char *) xml_merge, "all"))
424             merge = Metadata_merge_all;
425         else if (!strcmp((const char *) xml_merge, "first"))
426             merge = Metadata_merge_first;
427         else
428         {
429             yaz_log(YLOG_FATAL,
430                     "Unknown value for metadata/merge: %s", xml_merge);
431             return -1;
432         }
433     }
434
435     if (xml_setting)
436     {
437         if (!strcmp((const char *) xml_setting, "no"))
438             setting = Metadata_setting_no;
439         else if (!strcmp((const char *) xml_setting, "postproc"))
440             setting = Metadata_setting_postproc;
441         else if (!strcmp((const char *) xml_setting, "parameter"))
442             setting = Metadata_setting_parameter;
443         else
444         {
445             yaz_log(YLOG_FATAL,
446                     "Unknown value for metadata/setting: %s", xml_setting);
447             return -1;
448         }
449     }
450
451     // add a sortkey if so specified
452     if (xml_sortkey && strcmp((const char *) xml_sortkey, "no"))
453     {
454         enum conf_metadata_type sk_type = type;
455         if (merge == Metadata_merge_no)
456         {
457             yaz_log(YLOG_FATAL,
458                     "Can't specify sortkey on a non-merged field");
459             return -1;
460         }
461         if (!strcmp((const char *) xml_sortkey, "yes"))
462             ;
463         if (!strcmp((const char *) xml_sortkey, "numeric"))
464             ;
465         else if (!strcmp((const char *) xml_sortkey, "skiparticle"))
466         {
467             if (sk_type == Metadata_type_generic)
468                 sk_type = Metadata_type_skiparticle;
469             else
470             {
471                 yaz_log(YLOG_FATAL,
472                         "skiparticle only supported for type=generic: %s",
473                     xml_type);
474                 return -1;
475             }
476         }
477         else
478         {
479             yaz_log(YLOG_FATAL,
480                     "Unknown sortkey in metadata element: %s",
481                     xml_sortkey);
482             return -1;
483         }
484         sortkey_offset = *sk_node;
485
486         conf_service_add_sortkey(service, *sk_node,
487                                  (const char *) xml_name, sk_type);
488         (*sk_node)++;
489     }
490     else
491         sortkey_offset = -1;
492
493     if (xml_mergekey)
494     {
495         if (!strcmp((const char *) xml_mergekey, "required"))
496             mergekey_type = Metadata_mergekey_required;
497         else if (!strcmp((const char *) xml_mergekey, "optional"))
498             mergekey_type = Metadata_mergekey_optional;
499         else if (!strcmp((const char *) xml_mergekey, "no"))
500             mergekey_type = Metadata_mergekey_no;
501         else
502         {
503             yaz_log(YLOG_FATAL, "Unknown value for mergekey: %s", xml_mergekey);
504             return -1;
505         }
506     }
507
508     // metadata known, assign values
509     conf_service_add_metadata(service, *md_node,
510                               (const char *) xml_name,
511                               type, merge, setting,
512                               brief, termlist,
513                               (const char *) xml_rank, sortkey_offset,
514                               mergekey_type,
515                               (const char *) xml_icu_chain,
516                               (const char *) xml_limitmap,
517                               (const char *) xml_limitcluster);
518     (*md_node)++;
519     return 0;
520 }
521
522
523 static void count_metadata(xmlNode *n, int *num_metadata, int *num_sortkeys)
524 {
525     xmlChar *sortkey = xmlGetProp(n, (xmlChar *) "sortkey");
526     (*num_metadata)++;
527
528     if (sortkey && strcmp((const char *) sortkey, "no"))
529         (*num_sortkeys)++;
530     xmlFree(sortkey);
531 }
532
533
534 static struct conf_service *service_create_static(struct conf_server *server,
535                                                   xmlNode *node,
536                                                   const char *service_id)
537 {
538     xmlNode *n;
539     int md_node = 0;
540     int sk_node = 0;
541
542     struct conf_service *service = 0;
543     int num_metadata = 0;
544     int num_sortkeys = 0;
545     int got_settings = 0;
546
547     // count num_metadata and num_sortkeys
548     for (n = node->children; n; n = n->next)
549         if (n->type == XML_ELEMENT_NODE && !strcmp((const char *)
550                                                    n->name, "metadata"))
551         {
552             if (n->children) // This is a <metadata> container, look at its contents.
553             {
554                 xmlNode *m;
555                 for (m = n->children; m; m = m->next)
556                 {
557                     if (m->type == XML_ELEMENT_NODE &&
558                             !strcmp((const char *) m->name, "metadata"))
559                         count_metadata(m, &num_metadata, &num_sortkeys);
560                 }
561             }
562             else // This is a metadata-element proper, count it right away.
563                 count_metadata(n, &num_metadata, &num_sortkeys);
564         }
565
566     service = service_init(server, num_metadata, num_sortkeys, service_id);
567
568     for (n = node->children; n; n = n->next)
569     {
570         if (n->type != XML_ELEMENT_NODE)
571             continue;
572         if (!strcmp((const char *) n->name, "timeout"))
573         {
574             xmlChar *src = xmlGetProp(n, (xmlChar *) "session");
575             if (src)
576             {
577                 service->session_timeout = atoi((const char *) src);
578                 xmlFree(src);
579                 if (service->session_timeout < 9)
580                 {
581                     yaz_log(YLOG_FATAL, "session timeout out of range");
582                     return 0;
583                 }
584             }
585             src = xmlGetProp(n, (xmlChar *) "z3950_operation");
586             if (src)
587             {
588                 service->z3950_operation_timeout = atoi((const char *) src);
589                 xmlFree(src);
590                 if (service->z3950_session_timeout < 9)
591                 {
592                     yaz_log(YLOG_FATAL, "Z39.50 operation timeout out of range");
593                     return 0;
594                 }
595             }
596             src = xmlGetProp(n, (xmlChar *) "z3950_session");
597             if (src)
598             {
599                 service->z3950_session_timeout = atoi((const char *) src);
600                 xmlFree(src);
601                 if (service->z3950_session_timeout < 9)
602                 {
603                     yaz_log(YLOG_FATAL, "Z39.50 session timeout out of range");
604                     return 0;
605                 }
606             }
607         }
608         else if (!strcmp((const char *) n->name, "ccldirective"))
609         {
610             char *name;
611             char *value;
612             if (!service->ccl_bibset)
613                 service->ccl_bibset = ccl_qual_mk();
614             name = (char *) xmlGetProp(n, (xmlChar *) "name");
615             if (!name)
616             {
617                 yaz_log(YLOG_FATAL, "ccldirective: missing @name");
618                 return 0;
619             }
620             value = (char *) xmlGetProp(n, (xmlChar *) "value");
621             if (!value)
622             {
623                 xmlFree(name);
624                 yaz_log(YLOG_FATAL, "ccldirective: missing @value");
625                 return 0;
626             }
627             ccl_qual_add_special(service->ccl_bibset, name, value);
628             xmlFree(value);
629             xmlFree(name);
630         }
631         else if (!strcmp((const char *) n->name, "settings"))
632             got_settings++;
633         else if (!strcmp((const char *) n->name, "icu_chain"))
634         {
635             if (!service->charsets)
636                 service->charsets = pp2_charset_fact_create();
637             if (pp2_charset_fact_define(service->charsets, n, 0))
638             {
639                 yaz_log(YLOG_FATAL, "ICU chain definition error");
640                 return 0;
641             }
642         }
643         else if (!strcmp((const char *) n->name, "relevance")
644                  || !strcmp((const char *) n->name, "sort")
645                  || !strcmp((const char *) n->name, "mergekey")
646                  || !strcmp((const char *) n->name, "facet"))
647
648         {
649             if (!service->charsets)
650                 service->charsets = pp2_charset_fact_create();
651             if (pp2_charset_fact_define(service->charsets, n,
652                                         (const char *) n->name))
653             {
654                 yaz_log(YLOG_FATAL, "ICU chain definition error");
655                 return 0;
656             }
657         }
658         else if (!strcmp((const char *) n->name, (const char *) "metadata"))
659         {
660             if (n->children) // This is a <metadata> container, look at its content.
661             {
662                 xmlNode *m;
663                 for (m = n->children; m; m = m->next)
664                     if ((!strcmp((const char *) m->name, (const char *) "metadata")))
665                         if (parse_metadata(service, m, &md_node, &sk_node))
666                             return 0;
667             }
668             else // This is a metadata-element proper, count it right away.
669                 if (parse_metadata(service, n, &md_node, &sk_node))
670                     return 0;
671         }
672         else if (!strcmp((const char *) n->name, (const char *) "xslt"))
673         {
674             if (service_xslt_config(service, n))
675                 return 0;
676         }
677         else if (!strcmp((const char *) n->name, "rank"))
678         {
679             char *rank_cluster = (char *) xmlGetProp(n, (xmlChar *) "cluster");
680             char *rank_debug = (char *) xmlGetProp(n, (xmlChar *) "debug");
681             char *rank_follow = (char *) xmlGetProp(n, (xmlChar *) "follow");
682             char *rank_lead = (char *) xmlGetProp(n, (xmlChar *) "lead");
683             char *rank_length= (char *) xmlGetProp(n, (xmlChar *) "length");
684             if (rank_cluster)
685             {
686                 if (!strcmp(rank_cluster, "yes"))
687                     service->rank_cluster = 1;
688                 else if (!strcmp(rank_cluster, "no"))
689                     service->rank_cluster = 0;
690                 else
691                 {
692                     yaz_log(YLOG_FATAL, "service: rank@cluster boolean");
693                     return 0;
694                 }
695             }
696             if (rank_debug)
697             {
698                 if (!strcmp(rank_debug, "yes"))
699                     service->rank_debug = 1;
700                 else if (!strcmp(rank_debug, "no"))
701                     service->rank_debug = 0;
702                 else
703                 {
704                     yaz_log(YLOG_FATAL, "service: rank@debug boolean");
705                     return 0;
706                 }
707             }
708             if (rank_follow)
709             {
710                 service->rank_follow = atof(rank_follow);
711             }
712             if (rank_lead)
713             {
714                 service->rank_lead = atof(rank_lead);
715             }
716             if (rank_length)
717             {
718                 if (!strcmp(rank_length, "linear"))
719                     service->rank_length = 2;
720                 else if (!strcmp(rank_length, "log"))
721                     service->rank_length = 1;
722                 else if (!strcmp(rank_length, "none"))
723                     service->rank_length = 0;
724                 else
725                 {
726                     yaz_log(YLOG_FATAL, "service: rank@length linear|log|none");
727                     return 0;
728                 }
729             }
730             xmlFree(rank_cluster);
731             xmlFree(rank_debug);
732             xmlFree(rank_follow);
733             xmlFree(rank_lead);
734             xmlFree(rank_length);
735         }
736         else if (!strcmp((const char *) n->name, "sort-default"))
737         {
738             char *default_sort = (char *) xmlGetProp(n, (xmlChar *) "field");
739
740             if (default_sort && strcmp(default_sort, "")) {
741                 service->default_sort = nmem_strdup(service->nmem, default_sort);
742                 yaz_log(YLOG_LOG, "service %s: default sort order configured to: %s",
743                         service_id ? service_id : "unnamed", default_sort);
744             }
745             else
746             {
747                 yaz_log(YLOG_FATAL, "default sort order is invalid: %s", default_sort);
748                 return 0;
749             }
750             xmlFree(default_sort);
751         }
752         else
753         {
754             char tmp[80];
755             yaz_log(YLOG_FATAL, "Bad element: %s . Context: %s", n->name,
756                     xml_context(n, tmp, sizeof tmp));
757             return 0;
758         }
759     }
760     if (got_settings)
761     {
762         int pass;
763         /* metadata has been read.. Consider now settings */
764         init_settings(service);
765         for (pass = 1; pass <= 2; pass++)
766         {
767             for (n = node->children; n; n = n->next)
768             {
769                 if (n->type != XML_ELEMENT_NODE)
770                     continue;
771                 if (!strcmp((const char *) n->name, "settings"))
772                 {
773                     int ret;
774                     xmlChar *src = xmlGetProp(n, (xmlChar *) "src");
775                     if (src)
776                     {
777                         WRBUF w = wrbuf_alloc();
778                         conf_dir_path(server->config, w, (const char *) src);
779                         ret = settings_read_file(service, wrbuf_cstr(w), pass);
780                         wrbuf_destroy(w);
781                         xmlFree(src);
782                     }
783                     else
784                     {
785                         ret = settings_read_node(service, n, pass);
786                     }
787                     if (ret)
788                         return 0;
789                 }
790             }
791         }
792     }
793
794     {
795         xmlBufferPtr buf = xmlBufferCreate();
796         xmlNodeDump(buf, node->doc, node, 0, 0);
797         service->xml_node =
798             nmem_strdupn(service->nmem, (const char *) buf->content, buf->use);
799         xmlBufferFree(buf);
800     }
801     return service;
802 }
803
804 static int inherit_server_settings(struct conf_service *s)
805 {
806     int ret = 0;
807     struct conf_server *server = s->server;
808     if (!s->dictionary) /* service has no config settings ? */
809     {
810         if (server->settings_fname)
811         {
812             /* inherit settings from server */
813             init_settings(s);
814             if (settings_read_file(s, server->settings_fname, 1))
815                 ret = -1;
816             if (settings_read_file(s, server->settings_fname, 2))
817                 ret = -1;
818         }
819         else
820         {
821             yaz_log(YLOG_WARN, "server '%s' has no settings", s->id ? s->id : "unnamed");
822             init_settings(s);
823         }
824     }
825
826     /* use relevance/sort/mergekey/facet from server if not defined
827        for this service.. */
828     if (!s->charsets)
829     {
830         if (server->charsets)
831         {
832             s->charsets = server->charsets;
833             pp2_charset_fact_incref(s->charsets);
834         }
835         else
836         {
837             s->charsets = pp2_charset_fact_create();
838         }
839     }
840     return ret;
841 }
842
843 struct conf_service *service_create(struct conf_server *server,
844                                     xmlNode *node)
845 {
846     struct conf_service *service = service_create_static(server, node, 0);
847     if (service)
848     {
849         inherit_server_settings(service);
850         assert(service->mutex == 0);
851         pazpar2_mutex_create(&service->mutex, "conf");
852     }
853     return service;
854 }
855
856 static struct conf_server *server_create(struct conf_config *config,
857                                          NMEM nmem, xmlNode *node)
858 {
859     xmlNode *n;
860     struct conf_server *server = nmem_malloc(nmem, sizeof(struct conf_server));
861     xmlChar *server_id = xmlGetProp(node, (xmlChar *) "id");
862
863     server->host = "@";
864     server->port = 0;
865     server->proxy_host = 0;
866     server->proxy_port = 0;
867     server->myurl = 0;
868     server->service = 0;
869     server->config = config;
870     server->next = 0;
871     server->charsets = 0;
872     server->http_server = 0;
873     server->iochan_man = 0;
874     server->settings_fname = 0;
875
876     if (server_id)
877     {
878         server->server_id = nmem_strdup(nmem, (const char *)server_id);
879         xmlFree(server_id);
880     }
881     else
882         server->server_id = 0;
883     for (n = node->children; n; n = n->next)
884     {
885         if (n->type != XML_ELEMENT_NODE)
886             continue;
887         if (!strcmp((const char *) n->name, "listen"))
888         {
889             xmlChar *port = xmlGetProp(n, (xmlChar *) "port");
890             xmlChar *host = xmlGetProp(n, (xmlChar *) "host");
891
892             if (port)
893                 server->port = nmem_strdup(nmem, (const char *) port);
894             if (host)
895                 server->host = nmem_strdup(nmem, (const char *) host);
896
897             xmlFree(port);
898             xmlFree(host);
899         }
900         else if (!strcmp((const char *) n->name, "proxy"))
901         {
902             xmlChar *port = xmlGetProp(n, (xmlChar *) "port");
903             xmlChar *host = xmlGetProp(n, (xmlChar *) "host");
904             xmlChar *myurl = xmlGetProp(n, (xmlChar *) "myurl");
905             if (port)
906                 server->proxy_port = atoi((const char *) port);
907             if (host)
908                 server->proxy_host = nmem_strdup(nmem, (const char *) host);
909             if (myurl)
910                 server->myurl = nmem_strdup(nmem, (const char *) myurl);
911             xmlFree(port);
912             xmlFree(host);
913             xmlFree(myurl);
914         }
915         else if (!strcmp((const char *) n->name, "settings"))
916         {
917             xmlChar *src = xmlGetProp(n, (xmlChar *) "src");
918             WRBUF w;
919             if (!src)
920             {
921                 yaz_log(YLOG_FATAL, "Missing src attribute for settings");
922                 return 0;
923             }
924             if (server->settings_fname)
925             {
926                 xmlFree(src);
927                 yaz_log(YLOG_FATAL, "Can't repeat 'settings'");
928                 return 0;
929             }
930             w = wrbuf_alloc();
931             conf_dir_path(config, w, (const char *) src);
932             server->settings_fname = nmem_strdup(nmem, wrbuf_cstr(w));
933             wrbuf_destroy(w);
934             xmlFree(src);
935         }
936         else if (!strcmp((const char *) n->name, "icu_chain"))
937         {
938             if (!server->charsets)
939                 server->charsets = pp2_charset_fact_create();
940             if (pp2_charset_fact_define(server->charsets, n, 0))
941             {
942                 yaz_log(YLOG_FATAL, "ICU chain definition error");
943                 return 0;
944             }
945         }
946         else if (!strcmp((const char *) n->name, "relevance")
947                  || !strcmp((const char *) n->name, "sort")
948                  || !strcmp((const char *) n->name, "mergekey")
949                  || !strcmp((const char *) n->name, "facet"))
950         {
951             if (!server->charsets)
952                 server->charsets = pp2_charset_fact_create();
953             if (pp2_charset_fact_define(server->charsets, n,
954                                         (const char *) n->name))
955             {
956                 yaz_log(YLOG_FATAL, "ICU chain definition error");
957                 return 0;
958             }
959         }
960         else if (!strcmp((const char *) n->name, "service"))
961         {
962             char *service_id = (char *)
963                 xmlGetProp(n, (xmlChar *) "id");
964
965             struct conf_service **sp = &server->service;
966             for (; *sp; sp = &(*sp)->next)
967                 if ((*sp)->id && service_id &&
968                     0 == strcmp((*sp)->id, service_id))
969                 {
970                     yaz_log(YLOG_FATAL, "Duplicate service: %s", service_id);
971                     break;
972                 }
973                 else if (!(*sp)->id && !service_id)
974                 {
975                     yaz_log(YLOG_FATAL, "Duplicate unnamed service");
976                     break;
977                 }
978
979             if (*sp)  /* service already exist */
980             {
981                 xmlFree(service_id);
982                 return 0;
983             }
984             else
985             {
986                 struct conf_service *s = service_create_static(server, n,
987                                                                service_id);
988                 xmlFree(service_id);
989                 if (!s)
990                     return 0;
991                 *sp = s;
992             }
993         }
994         else
995         {
996             yaz_log(YLOG_FATAL, "Bad element: %s", n->name);
997             return 0;
998         }
999     }
1000     if (!server->port)
1001     {
1002         yaz_log(YLOG_FATAL, "No listening port given");
1003         return 0;
1004     }
1005     if (server->service)
1006     {
1007         struct conf_service *s;
1008         for (s = server->service; s; s = s->next)
1009             inherit_server_settings(s);
1010     }
1011     return server;
1012 }
1013
1014 WRBUF conf_get_fname(struct conf_config *config, const char *fname)
1015 {
1016     WRBUF w = wrbuf_alloc();
1017
1018     conf_dir_path(config, w, fname);
1019     return w;
1020 }
1021
1022 struct conf_service *locate_service(struct conf_server *server,
1023                                     const char *service_id)
1024 {
1025     struct conf_service *s = server->service;
1026     for (; s; s = s->next)
1027         if (s->id && service_id && 0 == strcmp(s->id, service_id))
1028             break;
1029         else if (!s->id && !service_id)
1030             break;
1031     if (s)
1032         service_incref(s);
1033     return s;
1034 }
1035
1036 static void info_service_metadata(struct conf_service *service, WRBUF w)
1037 {
1038     int i;
1039     struct conf_metadata *md;
1040
1041     if (service->num_metadata)
1042     {
1043         for (i = 0; i < service->num_metadata; i++)
1044         {
1045             md = &(service->metadata[i]);
1046             wrbuf_puts(w, "   <metadata");
1047             if (md->name) {
1048                 wrbuf_puts(w, " name=\"");
1049                 wrbuf_xmlputs(w, md->name);
1050                 wrbuf_puts(w, "\"");
1051             }
1052             if (md->brief) {
1053                 wrbuf_puts(w, " brief=\"yes\"");
1054             }
1055             if (md->termlist) {
1056                 wrbuf_puts(w, " termlist=\"yes\"");
1057             }
1058             if (md->rank) {
1059                 wrbuf_puts(w, " rank=\"");
1060                 wrbuf_xmlputs(w, md->rank);
1061                 wrbuf_puts(w, "\"");
1062             }
1063             if (md->sortkey_offset > 0) {
1064                 wrbuf_puts(w, " sortkey=\"");
1065                 switch (service->sortkeys[md->sortkey_offset].type)
1066                 {
1067                 case Metadata_type_relevance:
1068                     wrbuf_puts(w, "relevance");
1069                     break;
1070                 case Metadata_type_skiparticle:
1071                     wrbuf_puts(w, "skiparticle");
1072                     break;
1073                 case Metadata_type_position:
1074                     wrbuf_puts(w, "position");
1075                     break;
1076                 default:
1077                     wrbuf_puts(w, "yes");
1078                     break;
1079                 }
1080                 wrbuf_puts(w, "\"");
1081             }
1082             switch (md->type)
1083             {
1084             case Metadata_type_generic:
1085             case Metadata_type_skiparticle:
1086                 break;
1087             case Metadata_type_year:
1088                 wrbuf_puts(w, " type=\"year\"");
1089                 break;
1090             case Metadata_type_date:
1091                 wrbuf_puts(w, " type=\"date\"");
1092                 break;
1093             case Metadata_type_float:
1094                 wrbuf_puts(w, " type=\"float\"");
1095                 break;
1096             case Metadata_type_relevance:
1097             case Metadata_type_position:
1098                 break;
1099             }
1100             switch (md->merge)
1101             {
1102             case Metadata_merge_no:
1103                 break;
1104             case Metadata_merge_unique:
1105                 wrbuf_puts(w, " merge=\"unique\"");
1106                 break;
1107             case Metadata_merge_longest:
1108                 wrbuf_puts(w, " merge=\"longest\"");
1109                 break;
1110             case Metadata_merge_range:
1111                 wrbuf_puts(w, " merge=\"range\"");
1112                 break;
1113             case Metadata_merge_all:
1114                 wrbuf_puts(w, " merge=\"all\"");
1115                 break;
1116             case Metadata_merge_first:
1117                 wrbuf_puts(w, " merge=\"first\"");
1118                 break;
1119             }
1120             switch (md->mergekey)
1121             {
1122             case Metadata_mergekey_no:
1123                 break;
1124             case Metadata_mergekey_optional:
1125                 wrbuf_puts(w, " mergekey=\"optional\"");
1126                 break;
1127             case Metadata_mergekey_required:
1128                 wrbuf_puts(w, " mergekey=\"required\"");
1129                 break;
1130             }
1131             wrbuf_puts(w, " />\n");
1132         }
1133     }
1134 }
1135
1136 static void info_service_databases(struct conf_service *service, WRBUF w)
1137 {
1138     struct database *db;
1139     struct setting *s;
1140     int i;
1141
1142     if (service->databases)
1143     {
1144         wrbuf_puts(w, "   <databases>\n");
1145         for(db = service->databases; db; db = db->next)
1146         {
1147             wrbuf_puts(w, "    <database");
1148             if (db->id)
1149             {
1150                 wrbuf_puts(w, " id=\"");
1151                 wrbuf_printf(w, "%s", db->id);
1152                 wrbuf_puts(w, "\"");
1153             }
1154             wrbuf_puts(w, ">\n");
1155             for (i = 0; i < db->num_settings; i++)
1156             {
1157                 s = db->settings[i];
1158                 while (s != NULL)
1159                 {
1160                     wrbuf_puts(w, "     <setting");
1161                     wrbuf_puts(w, " name=\"");
1162                     wrbuf_xmlputs(w, s->name);
1163                     wrbuf_puts(w, "\"");
1164                     wrbuf_puts(w, " value=\"");
1165                     wrbuf_xmlputs(w, s->value);
1166                     wrbuf_puts(w, "\"");
1167                     wrbuf_puts(w, " />\n");
1168                     s = s->next;
1169                 }
1170             }
1171             wrbuf_puts(w, "    </database>\n");
1172         }
1173         wrbuf_puts(w, "   </databases>\n");
1174     }
1175 }
1176
1177 void info_services(struct conf_server *server, WRBUF w)
1178 {
1179     struct conf_service *s = server->service;
1180     int i;
1181     struct setting *S;
1182
1183     wrbuf_puts(w, " <services>\n");
1184     for (; s; s = s->next)
1185     {
1186         wrbuf_puts(w, "  <service");
1187         if (s->id)
1188         {
1189             wrbuf_puts(w, " id=\"");
1190             wrbuf_xmlputs(w, s->id);
1191             wrbuf_puts(w, "\"");
1192         }
1193         wrbuf_puts(w, ">\n");
1194         if (s->settings)
1195         {
1196             for (i=0; i<s->settings->num_settings; i++)
1197             {
1198                 S = s->settings->settings[i];
1199                 while (S != NULL) {
1200                     wrbuf_puts(w, "   <setting");
1201                     wrbuf_puts(w, " name=\"");
1202                     wrbuf_xmlputs(w,  S->name);
1203                     wrbuf_puts(w, "\"");
1204                     wrbuf_puts(w, " value=\"");
1205                     wrbuf_xmlputs(w, S->value);
1206                     wrbuf_puts(w, "\"");
1207                     if (S->target) {
1208                         wrbuf_puts(w, " target=\"");
1209                         wrbuf_xmlputs(w, S->target);
1210                         wrbuf_puts(w, "\"");
1211                     }
1212
1213                     wrbuf_puts(w, " />\n");
1214
1215                     S = S->next;
1216                 }
1217             }
1218         }
1219         info_service_metadata(s, w);
1220         info_service_databases(s, w);
1221         wrbuf_puts(w, "  </service>");
1222
1223         wrbuf_puts(w, "\n");
1224     }
1225     wrbuf_puts(w, " </services>\n");
1226 }
1227
1228 static int parse_config(struct conf_config *config, xmlNode *root)
1229 {
1230     xmlNode *n;
1231
1232     for (n = root->children; n; n = n->next)
1233     {
1234         if (n->type != XML_ELEMENT_NODE)
1235             continue;
1236         if (!strcmp((const char *) n->name, "server"))
1237         {
1238             struct conf_server *tmp = server_create(config, config->nmem, n);
1239             if (!tmp)
1240                 return -1;
1241             tmp->next = config->servers;
1242             config->servers = tmp;
1243         }
1244         else if (!strcmp((const char *) n->name, "threads"))
1245         {
1246             xmlChar *number = xmlGetProp(n, (xmlChar *) "number");
1247             if (number)
1248             {
1249                 config->no_threads = atoi((const char *) number);
1250                 xmlFree(number);
1251             }
1252         }
1253         else if (!strcmp((const char *) n->name, "file"))
1254         {
1255             xmlChar *path = xmlGetProp(n, (xmlChar *) "path");
1256             if (path)
1257             {
1258                 config->path = nmem_strdup(config->nmem, (const char *) path);
1259                 xmlFree(path);
1260             }
1261         }
1262         else if (!strcmp((const char *) n->name, "targetprofiles"))
1263         {
1264             yaz_log(YLOG_FATAL, "targetprofiles unsupported here. Must be part of service");
1265             return -1;
1266
1267         }
1268         else
1269         {
1270             yaz_log(YLOG_FATAL, "Bad element: %s", n->name);
1271             return -1;
1272         }
1273     }
1274     return 0;
1275 }
1276
1277 struct conf_config *config_create(const char *fname)
1278 {
1279     xmlDoc *doc = xmlReadFile(fname,
1280                               NULL,
1281                               XML_PARSE_XINCLUDE
1282                               + XML_PARSE_NSCLEAN + XML_PARSE_NONET);
1283     xmlNode *n;
1284     const char *p;
1285     int r;
1286     NMEM nmem = nmem_create();
1287     struct conf_config *config = nmem_malloc(nmem, sizeof(struct conf_config));
1288
1289     xmlSubstituteEntitiesDefault(1);
1290     xmlLoadExtDtdDefaultValue = 1;
1291     if (!doc)
1292     {
1293         yaz_log(YLOG_FATAL, "Failed to read %s", fname);
1294         nmem_destroy(nmem);
1295         return 0;
1296     }
1297
1298     // Perform XInclude.
1299     r = xmlXIncludeProcess(doc);
1300     if (r == -1)
1301     {
1302         yaz_log(YLOG_FATAL, "XInclude processing failed");
1303         return 0;
1304     }
1305
1306     config->nmem = nmem;
1307     config->servers = 0;
1308     config->path = nmem_strdup(nmem, ".");
1309     config->no_threads = 0;
1310     config->iochan_man = 0;
1311
1312     config->confdir = wrbuf_alloc();
1313     if ((p = strrchr(fname,
1314 #ifdef WIN32
1315                      '\\'
1316 #else
1317                      '/'
1318 #endif
1319              )))
1320     {
1321         int len = p - fname;
1322         wrbuf_write(config->confdir, fname, len);
1323     }
1324     wrbuf_puts(config->confdir, "");
1325
1326     n = xmlDocGetRootElement(doc);
1327     r = yaz_xml_include_simple(n, wrbuf_cstr(config->confdir));
1328     if (r == 0) /* OK */
1329     {
1330         yaz_log(YLOG_LOG, "Configuration %s after include processing",
1331                 fname);
1332 #if LIBXML_VERSION >= 20600
1333         xmlDocFormatDump(yaz_log_file(), doc, 0);
1334 #else
1335         xmlDocDump(yaz_log_file(), doc);
1336 #endif
1337         r = parse_config(config, n);
1338     }
1339     xmlFreeDoc(doc);
1340
1341     if (r)
1342     {
1343         config_destroy(config);
1344         return 0;
1345     }
1346     return config;
1347 }
1348
1349 void server_destroy(struct conf_server *server)
1350 {
1351     struct conf_service *s = server->service;
1352     while (s)
1353     {
1354         struct conf_service *s_next = s->next;
1355         service_destroy(s);
1356         s = s_next;
1357     }
1358     pp2_charset_fact_destroy(server->charsets);
1359     http_server_destroy(server->http_server);
1360 }
1361
1362 void config_destroy(struct conf_config *config)
1363 {
1364     if (config)
1365     {
1366         struct conf_server *server = config->servers;
1367         iochan_man_destroy(&config->iochan_man);
1368         while (server)
1369         {
1370             struct conf_server *s_next = server->next;
1371             server_destroy(server);
1372             server = s_next;
1373         }
1374         wrbuf_destroy(config->confdir);
1375         nmem_destroy(config->nmem);
1376     }
1377 }
1378
1379 void config_stop_listeners(struct conf_config *conf)
1380 {
1381     struct conf_server *ser;
1382     for (ser = conf->servers; ser; ser = ser->next)
1383         http_close_server(ser);
1384 }
1385
1386 void config_process_events(struct conf_config *conf)
1387 {
1388     struct conf_server *ser;
1389
1390     for (ser = conf->servers; ser; ser = ser->next)
1391     {
1392         struct conf_service *s = ser->service;
1393
1394         for (;s ; s = s->next)
1395         {
1396             assert(s->mutex == 0);
1397             pazpar2_mutex_create(&s->mutex, "service");
1398         }
1399         http_mutex_init(ser);
1400     }
1401     iochan_man_events(conf->iochan_man);
1402 }
1403
1404 int config_start_listeners(struct conf_config *conf,
1405                            const char *listener_override,
1406                            const char *record_fname)
1407 {
1408     struct conf_server *ser;
1409
1410     conf->iochan_man = iochan_man_create(conf->no_threads);
1411     for (ser = conf->servers; ser; ser = ser->next)
1412     {
1413         WRBUF w;
1414         int r;
1415
1416         ser->iochan_man = conf->iochan_man;
1417         if (listener_override)
1418         {
1419             const char *cp = strrchr(listener_override, ':');
1420             if (cp)
1421             {
1422                 ser->host = nmem_strdupn(conf->nmem, listener_override,
1423                                          cp - listener_override);
1424                 ser->port = nmem_strdup(conf->nmem, cp + 1);
1425             }
1426             else
1427             {
1428                 ser->host = "@";
1429                 ser->port = nmem_strdup(conf->nmem, listener_override);
1430             }
1431             listener_override = 0; /* only first server is overriden */
1432         }
1433         r = http_init(ser, record_fname);
1434         if (r)
1435             return -1;
1436
1437         w = wrbuf_alloc();
1438         if (ser->proxy_host || ser->proxy_port)
1439         {
1440             if (ser->proxy_host)
1441                 wrbuf_puts(w, ser->proxy_host);
1442             if (ser->proxy_port)
1443             {
1444                 if (wrbuf_len(w))
1445                     wrbuf_puts(w, ":");
1446                 wrbuf_printf(w, "%d", ser->proxy_port);
1447             }
1448         }
1449         if (wrbuf_len(w))
1450             http_set_proxyaddr(wrbuf_cstr(w), ser);
1451         wrbuf_destroy(w);
1452     }
1453     return 0;
1454 }
1455
1456 /*
1457  * Local variables:
1458  * c-basic-offset: 4
1459  * c-file-style: "Stroustrup"
1460  * indent-tabs-mode: nil
1461  * End:
1462  * vim: shiftwidth=4 tabstop=8 expandtab
1463  */
1464