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