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