New merge attribute type: 'first'
[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 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 if (!strcmp((const char *) xml_merge, "first"))
389             merge = Metadata_merge_first;
390         else
391         {
392             yaz_log(YLOG_FATAL,
393                     "Unknown value for metadata/merge: %s", xml_merge);
394             return -1;
395         }
396     }
397
398     if (xml_setting)
399     {
400         if (!strcmp((const char *) xml_setting, "no"))
401             setting = Metadata_setting_no;
402         else if (!strcmp((const char *) xml_setting, "postproc"))
403             setting = Metadata_setting_postproc;
404         else if (!strcmp((const char *) xml_setting, "parameter"))
405             setting = Metadata_setting_parameter;
406         else
407         {
408             yaz_log(YLOG_FATAL,
409                     "Unknown value for medadata/setting: %s", xml_setting);
410             return -1;
411         }
412     }
413
414     // add a sortkey if so specified
415     if (xml_sortkey && strcmp((const char *) xml_sortkey, "no"))
416     {
417         enum conf_sortkey_type sk_type;
418         if (merge == Metadata_merge_no)
419         {
420             yaz_log(YLOG_FATAL,
421                     "Can't specify sortkey on a non-merged field");
422             return -1;
423         }
424         if (!strcmp((const char *) xml_sortkey, "numeric"))
425             sk_type = Metadata_sortkey_numeric;
426         else if (!strcmp((const char *) xml_sortkey, "skiparticle"))
427             sk_type = Metadata_sortkey_skiparticle;
428         else
429         {
430             yaz_log(YLOG_FATAL,
431                     "Unknown sortkey in metadata element: %s",
432                     xml_sortkey);
433             return -1;
434         }
435         sortkey_offset = *sk_node;
436
437         conf_service_add_sortkey(service, *sk_node,
438                                  (const char *) xml_name, sk_type);
439         (*sk_node)++;
440     }
441     else
442         sortkey_offset = -1;
443
444     if (xml_mergekey)
445     {
446         if (!strcmp((const char *) xml_mergekey, "required"))
447             mergekey_type = Metadata_mergekey_required;
448         else if (!strcmp((const char *) xml_mergekey, "optional"))
449             mergekey_type = Metadata_mergekey_optional;
450         else if (!strcmp((const char *) xml_mergekey, "no"))
451             mergekey_type = Metadata_mergekey_no;
452         else
453         {
454             yaz_log(YLOG_FATAL, "Unknown value for mergekey: %s", xml_mergekey);
455             return -1;
456         }
457     }
458
459     // metadata known, assign values
460     conf_service_add_metadata(service, *md_node,
461                               (const char *) xml_name,
462                               type, merge, setting,
463                               brief, termlist,
464                               (const char *) xml_rank, sortkey_offset,
465                               mergekey_type,
466                               (const char *) xml_icu_chain,
467                               (const char *) xml_limitmap,
468                               (const char *) xml_limitcluster);
469     (*md_node)++;
470     return 0;
471 }
472
473 static struct conf_service *service_create_static(struct conf_server *server,
474                                                   xmlNode *node,
475                                                   const char *service_id)
476 {
477     xmlNode *n;
478     int md_node = 0;
479     int sk_node = 0;
480
481     struct conf_service *service = 0;
482     int num_metadata = 0;
483     int num_sortkeys = 0;
484     int got_settings = 0;
485
486     // count num_metadata and num_sortkeys
487     for (n = node->children; n; n = n->next)
488         if (n->type == XML_ELEMENT_NODE && !strcmp((const char *)
489                                                    n->name, "metadata"))
490         {
491             xmlChar *sortkey = xmlGetProp(n, (xmlChar *) "sortkey");
492             num_metadata++;
493             if (sortkey && strcmp((const char *) sortkey, "no"))
494                 num_sortkeys++;
495             xmlFree(sortkey);
496         }
497
498     service = service_init(server, num_metadata, num_sortkeys, service_id);
499
500     for (n = node->children; n; n = n->next)
501     {
502         if (n->type != XML_ELEMENT_NODE)
503             continue;
504         if (!strcmp((const char *) n->name, "timeout"))
505         {
506             xmlChar *src = xmlGetProp(n, (xmlChar *) "session");
507             if (src)
508             {
509                 service->session_timeout = atoi((const char *) src);
510                 xmlFree(src);
511                 if (service->session_timeout < 9)
512                 {
513                     yaz_log(YLOG_FATAL, "session timeout out of range");
514                     return 0;
515                 }
516             }
517             src = xmlGetProp(n, (xmlChar *) "z3950_operation");
518             if (src)
519             {
520                 service->z3950_operation_timeout = atoi((const char *) src);
521                 xmlFree(src);
522                 if (service->z3950_session_timeout < 9)
523                 {
524                     yaz_log(YLOG_FATAL, "Z39.50 operation timeout out of range");
525                     return 0;
526                 }
527             }
528             src = xmlGetProp(n, (xmlChar *) "z3950_session");
529             if (src)
530             {
531                 service->z3950_session_timeout = atoi((const char *) src);
532                 xmlFree(src);
533                 if (service->z3950_session_timeout < 9)
534                 {
535                     yaz_log(YLOG_FATAL, "Z39.50 session timeout out of range");
536                     return 0;
537                 }
538             }
539         }
540         else if (!strcmp((const char *) n->name, "ccldirective"))
541         {
542             char *name;
543             char *value;
544             if (!service->ccl_bibset)
545                 service->ccl_bibset = ccl_qual_mk();
546             name = (char *) xmlGetProp(n, (xmlChar *) "name");
547             if (!name)
548             {
549                 yaz_log(YLOG_FATAL, "ccldirective: missing @name");
550                 return 0;
551             }
552             value = (char *) xmlGetProp(n, (xmlChar *) "value");
553             if (!value)
554             {
555                 xmlFree(name);
556                 yaz_log(YLOG_FATAL, "ccldirective: missing @value");
557                 return 0;
558             }
559             ccl_qual_add_special(service->ccl_bibset, name, value);
560             xmlFree(value);
561             xmlFree(name);
562         }
563         else if (!strcmp((const char *) n->name, "settings"))
564             got_settings++;
565         else if (!strcmp((const char *) n->name, "icu_chain"))
566         {
567             if (!service->charsets)
568                 service->charsets = pp2_charset_fact_create();
569             if (pp2_charset_fact_define(service->charsets, n, 0))
570             {
571                 yaz_log(YLOG_FATAL, "ICU chain definition error");
572                 return 0;
573             }
574         }
575         else if (!strcmp((const char *) n->name, "relevance")
576                  || !strcmp((const char *) n->name, "sort")
577                  || !strcmp((const char *) n->name, "mergekey")
578                  || !strcmp((const char *) n->name, "facet"))
579
580         {
581             if (!service->charsets)
582                 service->charsets = pp2_charset_fact_create();
583             if (pp2_charset_fact_define(service->charsets,
584                                         n->children, (const char *) n->name))
585             {
586                 yaz_log(YLOG_FATAL, "ICU chain definition error");
587                 return 0;
588             }
589         }
590         else if (!strcmp((const char *) n->name, (const char *) "metadata"))
591         {
592             if (parse_metadata(service, n, &md_node, &sk_node))
593                 return 0;
594         }
595         else if (!strcmp((const char *) n->name, (const char *) "xslt"))
596         {
597             if (service_xslt_config(service, n))
598                 return 0;
599         }
600         else if (!strcmp((const char *) n->name, (const char *) "set"))
601         {
602             xmlChar *name= xmlGetProp(n, (xmlChar *) "name");
603             xmlChar *value = xmlGetProp(n, (xmlChar *) "value");
604             if (service->dictionary && name && value) {
605                 yaz_log(YLOG_DEBUG, "service set: %s=%s (Not implemented)", (char *) name, (char *) value);
606                 //service_aply_setting(service, name, value);
607             }
608         }
609         else if (!strcmp((const char *) n->name, "rank"))
610         {
611             char *rank_cluster = (char *) xmlGetProp(n, (xmlChar *) "cluster");
612             char *rank_debug = (char *) xmlGetProp(n, (xmlChar *) "debug");
613             char *rank_follow = (char *) xmlGetProp(n, (xmlChar *) "follow");
614             char *rank_lead = (char *) xmlGetProp(n, (xmlChar *) "lead");
615             char *rank_length= (char *) xmlGetProp(n, (xmlChar *) "length");
616             if (rank_cluster)
617             {
618                 if (!strcmp(rank_cluster, "yes"))
619                     service->rank_cluster = 1;
620                 else if (!strcmp(rank_cluster, "no"))
621                     service->rank_cluster = 0;
622                 else 
623                 {
624                     yaz_log(YLOG_FATAL, "service: rank@cluster boolean");
625                     return 0;
626                 }
627             }
628             if (rank_debug)
629             {
630                 if (!strcmp(rank_debug, "yes"))
631                     service->rank_debug = 1;
632                 else if (!strcmp(rank_debug, "no"))
633                     service->rank_debug = 0;
634                 else
635                 {
636                     yaz_log(YLOG_FATAL, "service: rank@debug boolean");
637                     return 0;
638                 }
639             }
640             if (rank_follow)
641             {
642                 service->rank_follow = atof(rank_follow);
643             }
644             if (rank_lead)
645             {
646                 service->rank_lead = atof(rank_lead);
647             }
648             if (rank_length)
649             {
650                 if (!strcmp(rank_length, "linear"))
651                     service->rank_length = 2;
652                 else if (!strcmp(rank_length, "log"))
653                     service->rank_length = 1; 
654                 else if (!strcmp(rank_length, "none"))
655                     service->rank_length = 0;
656                 else
657                 {
658                     yaz_log(YLOG_FATAL, "service: rank@length linear|log|none");
659                     return 0;
660                 }
661             }
662             xmlFree(rank_cluster);
663             xmlFree(rank_debug);
664             xmlFree(rank_follow);
665             xmlFree(rank_lead);
666             xmlFree(rank_length);
667         }
668         else if (!strcmp((const char *) n->name, "sort-default"))
669         {
670             char *default_sort = (char *) xmlGetProp(n, (xmlChar *) "field");
671
672             if (default_sort && strcmp(default_sort, "")) {
673                 service->default_sort = nmem_strdup(service->nmem, default_sort);
674                 yaz_log(YLOG_LOG, "service %s: default sort order configured to: %s",
675                         service_id ? service_id : "unnamed", default_sort);
676             }
677             else
678             {
679                 yaz_log(YLOG_FATAL, "default sort order is invalid: %s", default_sort);
680                 return 0;
681             }
682             xmlFree(default_sort);
683         }
684         else
685         {
686             yaz_log(YLOG_FATAL, "Bad element: %s", n->name);
687             return 0;
688         }
689     }
690     if (got_settings)
691     {
692         int pass;
693         /* metadata has been read.. Consider now settings */
694         init_settings(service);
695         for (pass = 1; pass <= 2; pass++)
696         {
697             for (n = node->children; n; n = n->next)
698             {
699                 if (n->type != XML_ELEMENT_NODE)
700                     continue;
701                 if (!strcmp((const char *) n->name, "settings"))
702                 {
703                     int ret;
704                     xmlChar *src = xmlGetProp(n, (xmlChar *) "src");
705                     if (src)
706                     {
707                         WRBUF w = wrbuf_alloc();
708                         conf_dir_path(server->config, w, (const char *) src);
709                         ret = settings_read_file(service, wrbuf_cstr(w), pass);
710                         wrbuf_destroy(w);
711                         xmlFree(src);
712                     }
713                     else
714                     {
715                         ret = settings_read_node(service, n, pass);
716                     }
717                     if (ret)
718                         return 0;
719                 }
720             }
721         }
722     }
723     return service;
724 }
725
726 static int inherit_server_settings(struct conf_service *s)
727 {
728     int ret = 0;
729     struct conf_server *server = s->server;
730     if (!s->dictionary) /* service has no config settings ? */
731     {
732         if (server->settings_fname)
733         {
734             /* inherit settings from server */
735             init_settings(s);
736             if (settings_read_file(s, server->settings_fname, 1))
737                 ret = -1;
738             if (settings_read_file(s, server->settings_fname, 2))
739                 ret = -1;
740         }
741         else
742         {
743             yaz_log(YLOG_WARN, "server '%s' has no settings", s->id ? s->id : "unnamed");
744             init_settings(s);
745         }
746     }
747
748     /* use relevance/sort/mergekey/facet from server if not defined
749        for this service.. */
750     if (!s->charsets)
751     {
752         if (server->charsets)
753         {
754             s->charsets = server->charsets;
755             pp2_charset_fact_incref(s->charsets);
756         }
757         else
758         {
759             s->charsets = pp2_charset_fact_create();
760         }
761     }
762     return ret;
763 }
764
765 struct conf_service *service_create(struct conf_server *server,
766                                     xmlNode *node)
767 {
768     struct conf_service *service = service_create_static(server, node, 0);
769     if (service)
770     {
771         inherit_server_settings(service);
772         assert(service->mutex == 0);
773         pazpar2_mutex_create(&service->mutex, "conf");
774     }
775     return service;
776 }
777
778 static struct conf_server *server_create(struct conf_config *config,
779                                          NMEM nmem, xmlNode *node)
780 {
781     xmlNode *n;
782     struct conf_server *server = nmem_malloc(nmem, sizeof(struct conf_server));
783     xmlChar *server_id = xmlGetProp(node, (xmlChar *) "id");
784
785     server->host = 0;
786     server->port = 0;
787     server->proxy_host = 0;
788     server->proxy_port = 0;
789     server->myurl = 0;
790     server->service = 0;
791     server->config = config;
792     server->next = 0;
793     server->charsets = 0;
794     server->http_server = 0;
795     server->iochan_man = 0;
796     server->database_hosts = config->database_hosts;
797     server->settings_fname = 0;
798
799     if (server_id)
800     {
801         server->server_id = nmem_strdup(nmem, (const char *)server_id);
802         xmlFree(server_id);
803     }
804     else
805         server->server_id = 0;
806     for (n = node->children; n; n = n->next)
807     {
808         if (n->type != XML_ELEMENT_NODE)
809             continue;
810         if (!strcmp((const char *) n->name, "listen"))
811         {
812             xmlChar *port = xmlGetProp(n, (xmlChar *) "port");
813             xmlChar *host = xmlGetProp(n, (xmlChar *) "host");
814             if (port)
815                 server->port = atoi((const char *) port);
816             if (host)
817                 server->host = nmem_strdup(nmem, (const char *) host);
818             xmlFree(port);
819             xmlFree(host);
820         }
821         else if (!strcmp((const char *) n->name, "proxy"))
822         {
823             xmlChar *port = xmlGetProp(n, (xmlChar *) "port");
824             xmlChar *host = xmlGetProp(n, (xmlChar *) "host");
825             xmlChar *myurl = xmlGetProp(n, (xmlChar *) "myurl");
826             if (port)
827                 server->proxy_port = atoi((const char *) port);
828             if (host)
829                 server->proxy_host = nmem_strdup(nmem, (const char *) host);
830             if (myurl)
831                 server->myurl = nmem_strdup(nmem, (const char *) myurl);
832             xmlFree(port);
833             xmlFree(host);
834             xmlFree(myurl);
835         }
836         else if (!strcmp((const char *) n->name, "settings"))
837         {
838             xmlChar *src = xmlGetProp(n, (xmlChar *) "src");
839             WRBUF w;
840             if (!src)
841             {
842                 yaz_log(YLOG_FATAL, "Missing src attribute for settings");
843                 return 0;
844             }
845             if (server->settings_fname)
846             {
847                 xmlFree(src);
848                 yaz_log(YLOG_FATAL, "Can't repeat 'settings'");
849                 return 0;
850             }
851             w = wrbuf_alloc();
852             conf_dir_path(config, w, (const char *) src);
853             server->settings_fname = nmem_strdup(nmem, wrbuf_cstr(w));
854             wrbuf_destroy(w);
855             xmlFree(src);
856         }
857         else if (!strcmp((const char *) n->name, "icu_chain"))
858         {
859             if (!server->charsets)
860                 server->charsets = pp2_charset_fact_create();
861             if (pp2_charset_fact_define(server->charsets, n, 0))
862             {
863                 yaz_log(YLOG_FATAL, "ICU chain definition error");
864                 return 0;
865             }
866         }
867         else if (!strcmp((const char *) n->name, "relevance")
868                  || !strcmp((const char *) n->name, "sort")
869                  || !strcmp((const char *) n->name, "mergekey")
870                  || !strcmp((const char *) n->name, "facet"))
871         {
872             if (!server->charsets)
873                 server->charsets = pp2_charset_fact_create();
874             if (pp2_charset_fact_define(server->charsets,
875                                         n->children, (const char *) n->name))
876             {
877                 yaz_log(YLOG_FATAL, "ICU chain definition error");
878                 return 0;
879             }
880         }
881         else if (!strcmp((const char *) n->name, "service"))
882         {
883             char *service_id = (char *)
884                 xmlGetProp(n, (xmlChar *) "id");
885
886             struct conf_service **sp = &server->service;
887             for (; *sp; sp = &(*sp)->next)
888                 if ((*sp)->id && service_id &&
889                     0 == strcmp((*sp)->id, service_id))
890                 {
891                     yaz_log(YLOG_FATAL, "Duplicate service: %s", service_id);
892                     break;
893                 }
894                 else if (!(*sp)->id && !service_id)
895                 {
896                     yaz_log(YLOG_FATAL, "Duplicate unnamed service");
897                     break;
898                 }
899
900             if (*sp)  /* service already exist */
901             {
902                 xmlFree(service_id);
903                 return 0;
904             }
905             else
906             {
907                 struct conf_service *s = service_create_static(server, n,
908                                                                service_id);
909                 xmlFree(service_id);
910                 if (!s)
911                     return 0;
912                 *sp = s;
913             }
914         }
915         else
916         {
917             yaz_log(YLOG_FATAL, "Bad element: %s", n->name);
918             return 0;
919         }
920     }
921     if (server->service)
922     {
923         struct conf_service *s;
924         for (s = server->service; s; s = s->next)
925             inherit_server_settings(s);
926     }
927     return server;
928 }
929
930 WRBUF conf_get_fname(struct conf_config *config, const char *fname)
931 {
932     WRBUF w = wrbuf_alloc();
933
934     conf_dir_path(config, w, fname);
935     return w;
936 }
937
938 struct conf_service *locate_service(struct conf_server *server,
939                                     const char *service_id)
940 {
941     struct conf_service *s = server->service;
942     for (; s; s = s->next)
943         if (s->id && service_id && 0 == strcmp(s->id, service_id))
944             break;
945         else if (!s->id && !service_id)
946             break;
947     if (s)
948         service_incref(s);
949     return s;
950 }
951
952 void info_services(struct conf_server *server, WRBUF w)
953 {
954     struct conf_service *s = server->service;
955     wrbuf_puts(w, " <services>\n");
956     for (; s; s = s->next)
957     {
958         wrbuf_puts(w, "  <service");
959         if (s->id)
960         {
961             wrbuf_puts(w, " id=\"");
962             wrbuf_xmlputs(w, s->id);
963             wrbuf_puts(w, "\"");
964         }
965         wrbuf_puts(w, "/>");
966
967         wrbuf_puts(w, "\n");
968     }
969     wrbuf_puts(w, " </services>\n");
970 }
971
972 static int parse_config(struct conf_config *config, xmlNode *root)
973 {
974     xmlNode *n;
975
976     for (n = root->children; n; n = n->next)
977     {
978         if (n->type != XML_ELEMENT_NODE)
979             continue;
980         if (!strcmp((const char *) n->name, "server"))
981         {
982             struct conf_server *tmp = server_create(config, config->nmem, n);
983             if (!tmp)
984                 return -1;
985             tmp->next = config->servers;
986             config->servers = tmp;
987         }
988         else if (!strcmp((const char *) n->name, "threads"))
989         {
990             xmlChar *number = xmlGetProp(n, (xmlChar *) "number");
991             if (number)
992             {
993                 config->no_threads = atoi((const char *) number);
994                 xmlFree(number);
995             }
996         }
997         else if (!strcmp((const char *) n->name, "targetprofiles"))
998         {
999             yaz_log(YLOG_FATAL, "targetprofiles unsupported here. Must be part of service");
1000             return -1;
1001
1002         }
1003         else
1004         {
1005             yaz_log(YLOG_FATAL, "Bad element: %s", n->name);
1006             return -1;
1007         }
1008     }
1009     return 0;
1010 }
1011
1012 struct conf_config *config_create(const char *fname, int verbose)
1013 {
1014     xmlDoc *doc = xmlParseFile(fname);
1015     xmlNode *n;
1016     const char *p;
1017     int r;
1018     NMEM nmem = nmem_create();
1019     struct conf_config *config = nmem_malloc(nmem, sizeof(struct conf_config));
1020
1021     xmlSubstituteEntitiesDefault(1);
1022     xmlLoadExtDtdDefaultValue = 1;
1023     if (!doc)
1024     {
1025         yaz_log(YLOG_FATAL, "Failed to read %s", fname);
1026         nmem_destroy(nmem);
1027         return 0;
1028     }
1029
1030     config->nmem = nmem;
1031     config->servers = 0;
1032     config->no_threads = 0;
1033     config->iochan_man = 0;
1034     config->database_hosts = database_hosts_create();
1035
1036     config->confdir = wrbuf_alloc();
1037     if ((p = strrchr(fname,
1038 #ifdef WIN32
1039                      '\\'
1040 #else
1041                      '/'
1042 #endif
1043              )))
1044     {
1045         int len = p - fname;
1046         wrbuf_write(config->confdir, fname, len);
1047     }
1048     wrbuf_puts(config->confdir, "");
1049
1050     n = xmlDocGetRootElement(doc);
1051     r = yaz_xml_include_simple(n, wrbuf_cstr(config->confdir));
1052     if (r == 0) /* OK */
1053     {
1054         if (verbose)
1055         {
1056             yaz_log(YLOG_LOG, "Configuration %s after include processing",
1057                     fname);
1058 #if LIBXML_VERSION >= 20600
1059             xmlDocFormatDump(yaz_log_file(), doc, 0);
1060 #else
1061             xmlDocDump(yaz_log_file(), doc);
1062 #endif
1063         }
1064         r = parse_config(config, n);
1065     }
1066     xmlFreeDoc(doc);
1067
1068     if (r)
1069     {
1070         config_destroy(config);
1071         return 0;
1072     }
1073     return config;
1074 }
1075
1076 void server_destroy(struct conf_server *server)
1077 {
1078     struct conf_service *s = server->service;
1079     while (s)
1080     {
1081         struct conf_service *s_next = s->next;
1082         service_destroy(s);
1083         s = s_next;
1084     }
1085     pp2_charset_fact_destroy(server->charsets);
1086     yaz_log(YLOG_LOG, "server_destroy server=%p", server);
1087     http_server_destroy(server->http_server);
1088 }
1089
1090 void config_destroy(struct conf_config *config)
1091 {
1092     if (config)
1093     {
1094         struct conf_server *server = config->servers;
1095         iochan_man_destroy(&config->iochan_man);
1096         while (server)
1097         {
1098             struct conf_server *s_next = server->next;
1099             server_destroy(server);
1100             server = s_next;
1101             database_hosts_destroy(&config->database_hosts);
1102         }
1103         wrbuf_destroy(config->confdir);
1104         nmem_destroy(config->nmem);
1105     }
1106 }
1107
1108 void config_stop_listeners(struct conf_config *conf)
1109 {
1110     struct conf_server *ser;
1111     for (ser = conf->servers; ser; ser = ser->next)
1112         http_close_server(ser);
1113 }
1114
1115 void config_process_events(struct conf_config *conf)
1116 {
1117     struct conf_server *ser;
1118
1119     for (ser = conf->servers; ser; ser = ser->next)
1120     {
1121         struct conf_service *s = ser->service;
1122
1123         for (;s ; s = s->next)
1124         {
1125             assert(s->mutex == 0);
1126             pazpar2_mutex_create(&s->mutex, "service");
1127         }
1128         http_mutex_init(ser);
1129     }
1130     iochan_man_events(conf->iochan_man);
1131 }
1132
1133 int config_start_listeners(struct conf_config *conf,
1134                            const char *listener_override,
1135                            const char *record_fname)
1136 {
1137     struct conf_server *ser;
1138
1139     conf->iochan_man = iochan_man_create(conf->no_threads);
1140     for (ser = conf->servers; ser; ser = ser->next)
1141     {
1142         WRBUF w = wrbuf_alloc();
1143         int r;
1144
1145         ser->iochan_man = conf->iochan_man;
1146         if (listener_override)
1147         {
1148             wrbuf_puts(w, listener_override);
1149             listener_override = 0; /* only first server is overriden */
1150         }
1151         else
1152         {
1153             if (ser->host)
1154                 wrbuf_puts(w, ser->host);
1155             if (ser->port)
1156             {
1157                 if (wrbuf_len(w))
1158                     wrbuf_puts(w, ":");
1159                 wrbuf_printf(w, "%d", ser->port);
1160             }
1161         }
1162         r = http_init(wrbuf_cstr(w), ser, record_fname);
1163         wrbuf_destroy(w);
1164         if (r)
1165             return -1;
1166
1167         w = wrbuf_alloc();
1168         if (ser->proxy_host || ser->proxy_port)
1169         {
1170             if (ser->proxy_host)
1171                 wrbuf_puts(w, ser->proxy_host);
1172             if (ser->proxy_port)
1173             {
1174                 if (wrbuf_len(w))
1175                     wrbuf_puts(w, ":");
1176                 wrbuf_printf(w, "%d", ser->proxy_port);
1177             }
1178         }
1179         if (wrbuf_len(w))
1180             http_set_proxyaddr(wrbuf_cstr(w), ser);
1181         wrbuf_destroy(w);
1182     }
1183     return 0;
1184 }
1185
1186 /*
1187  * Local variables:
1188  * c-basic-offset: 4
1189  * c-file-style: "Stroustrup"
1190  * indent-tabs-mode: nil
1191  * End:
1192  * vim: shiftwidth=4 tabstop=8 expandtab
1193  */
1194