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