Databases per-service.
[pazpar2-moved-to-github.git] / src / pazpar2_config.c
1 /* This file is part of Pazpar2.
2    Copyright (C) 2006-2009 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
26 #include <libxml/parser.h>
27 #include <libxml/tree.h>
28 #include <libxslt/xslt.h>
29 #include <libxslt/transform.h>
30 #include <libxslt/xsltutils.h>
31
32 #include <yaz/yaz-util.h>
33 #include <yaz/nmem.h>
34 #include <yaz/snprintf.h>
35 #include <yaz/tpath.h>
36
37 #define CONFIG_NOEXTERNS
38 #include "pazpar2_config.h"
39 #include "settings.h"
40
41 static char confdir[256] = ".";
42
43 struct conf_config *config = 0;
44
45
46 static char *parse_settings(NMEM nmem, xmlNode *node);
47
48 static 
49 struct conf_metadata * conf_metadata_assign(NMEM nmem, 
50                                             struct conf_metadata * metadata,
51                                             const char *name,
52                                             enum conf_metadata_type type,
53                                             enum conf_metadata_merge merge,
54                                             enum conf_setting_type setting,
55                                             int brief,
56                                             int termlist,
57                                             int rank,
58                                             int sortkey_offset,
59                                             enum conf_metadata_mergekey mt)
60 {
61     if (!nmem || !metadata || !name)
62         return 0;
63     
64     metadata->name = nmem_strdup(nmem, name);
65
66     metadata->type = type;
67
68     // enforcing that type_year is always range_merge
69     if (metadata->type == Metadata_type_year)
70         metadata->merge = Metadata_merge_range;
71     else
72         metadata->merge = merge;    
73
74     metadata->setting = setting;
75     metadata->brief = brief;   
76     metadata->termlist = termlist;
77     metadata->rank = rank;    
78     metadata->sortkey_offset = sortkey_offset;
79     metadata->mergekey = mt;
80     return metadata;
81 }
82
83
84 static
85 struct conf_sortkey * conf_sortkey_assign(NMEM nmem, 
86                                           struct conf_sortkey * sortkey,
87                                           const char *name,
88                                           enum conf_sortkey_type type)
89 {
90     if (!nmem || !sortkey || !name)
91         return 0;
92     
93     sortkey->name = nmem_strdup(nmem, name);
94     sortkey->type = type;
95
96     return sortkey;
97 }
98
99
100 struct conf_service * conf_service_create(int num_metadata, int num_sortkeys,
101     const char *service_id)
102 {
103     struct conf_service * service = 0;
104     NMEM nmem = nmem_create();
105
106     //assert(nmem);
107     
108     service = nmem_malloc(nmem, sizeof(struct conf_service));
109     service->nmem = nmem;
110     service->next = 0;
111     service->settings = 0;
112     service->databases = 0;
113
114     service->id = service_id ? nmem_strdup(nmem, service_id) : 0;
115     service->num_metadata = num_metadata;
116     service->metadata = 0;
117     if (service->num_metadata)
118       service->metadata 
119           = nmem_malloc(nmem, 
120                         sizeof(struct conf_metadata) * service->num_metadata);
121     service->num_sortkeys = num_sortkeys;
122     service->sortkeys = 0;
123     if (service->num_sortkeys)
124         service->sortkeys 
125             = nmem_malloc(nmem, 
126                           sizeof(struct conf_sortkey) * service->num_sortkeys);
127     service->dictionary = 0;
128     return service; 
129 }
130
131 struct conf_metadata* conf_service_add_metadata(struct conf_service *service,
132                                                 int field_id,
133                                                 const char *name,
134                                                 enum conf_metadata_type type,
135                                                 enum conf_metadata_merge merge,
136                                                 enum conf_setting_type setting,
137                                                 int brief,
138                                                 int termlist,
139                                                 int rank,
140                                                 int sortkey_offset,
141                                                 enum conf_metadata_mergekey mt)
142 {
143     struct conf_metadata * md = 0;
144
145     if (!service || !service->metadata || !service->num_metadata
146         || field_id < 0  || !(field_id < service->num_metadata))
147         return 0;
148
149     //md = &((service->metadata)[field_id]);
150     md = service->metadata + field_id;
151     md = conf_metadata_assign(service->nmem, md, name, type, merge, setting,
152                               brief, termlist, rank, sortkey_offset,
153                               mt);
154     return md;
155 }
156
157
158 struct conf_sortkey * conf_service_add_sortkey(struct conf_service *service,
159                                                int field_id,
160                                                const char *name,
161                                                enum conf_sortkey_type type)
162 {
163     struct conf_sortkey * sk = 0;
164
165     if (!service || !service->sortkeys || !service->num_sortkeys
166         || field_id < 0  || !(field_id < service->num_sortkeys))
167         return 0;
168
169     //sk = &((service->sortkeys)[field_id]);
170     sk = service->sortkeys + field_id;
171     sk = conf_sortkey_assign(service->nmem, sk, name, type);
172
173     return sk;
174 }
175
176
177 int conf_service_metadata_field_id(struct conf_service *service,
178                                    const char * name)
179 {
180     int i = 0;
181
182     if (!service || !service->metadata || !service->num_metadata)
183         return -1;
184
185     for(i = 0; i < service->num_metadata; i++) {
186         if (!strcmp(name, (service->metadata[i]).name))
187             return i;
188     }
189    
190     return -1;
191 }
192
193
194 int conf_service_sortkey_field_id(struct conf_service *service,
195                                   const char * name)
196 {
197     int i = 0;
198
199     if (!service || !service->sortkeys || !service->num_sortkeys)
200         return -1;
201
202     for(i = 0; i < service->num_sortkeys; i++) {
203         if (!strcmp(name, (service->sortkeys[i]).name))
204             return i;
205     }
206    
207     return -1;
208 }
209
210
211
212 /* Code to parse configuration file */
213 /* ==================================================== */
214
215 static struct conf_service *parse_service(xmlNode *node, const char *service_id)
216 {
217     xmlNode *n;
218     int md_node = 0;
219     int sk_node = 0;
220
221     struct conf_service *service = 0;
222     int num_metadata = 0;
223     int num_sortkeys = 0;
224     
225     // count num_metadata and num_sortkeys
226     for (n = node->children; n; n = n->next)
227         if (n->type == XML_ELEMENT_NODE && !strcmp((const char *)
228                                                    n->name, "metadata"))
229         {
230             xmlChar *sortkey = xmlGetProp(n, (xmlChar *) "sortkey");
231             num_metadata++;
232             if (sortkey && strcmp((const char *) sortkey, "no"))
233                 num_sortkeys++;
234             xmlFree(sortkey);
235         }
236
237     service = conf_service_create(num_metadata, num_sortkeys, service_id);
238
239     for (n = node->children; n; n = n->next)
240     {
241         if (n->type != XML_ELEMENT_NODE)
242             continue;
243         if (!strcmp((const char *) n->name, "settings"))
244         {
245             if (service->settings)
246             {
247                 yaz_log(YLOG_FATAL, "Can't repeat 'settings'");
248                 return 0;
249             }
250             service->settings = parse_settings(service->nmem, n);
251             if (!service->settings)
252                 return 0;
253         }
254         else if (!strcmp((const char *) n->name, (const char *) "metadata"))
255         {
256             xmlChar *xml_name = xmlGetProp(n, (xmlChar *) "name");
257             xmlChar *xml_brief = xmlGetProp(n, (xmlChar *) "brief");
258             xmlChar *xml_sortkey = xmlGetProp(n, (xmlChar *) "sortkey");
259             xmlChar *xml_merge = xmlGetProp(n, (xmlChar *) "merge");
260             xmlChar *xml_type = xmlGetProp(n, (xmlChar *) "type");
261             xmlChar *xml_termlist = xmlGetProp(n, (xmlChar *) "termlist");
262             xmlChar *xml_rank = xmlGetProp(n, (xmlChar *) "rank");
263             xmlChar *xml_setting = xmlGetProp(n, (xmlChar *) "setting");
264             xmlChar *xml_mergekey = xmlGetProp(n, (xmlChar *) "mergekey");
265
266             enum conf_metadata_type type = Metadata_type_generic;
267             enum conf_metadata_merge merge = Metadata_merge_no;
268             enum conf_setting_type setting = Metadata_setting_no;
269             enum conf_sortkey_type sk_type = Metadata_sortkey_relevance;
270             enum conf_metadata_mergekey mergekey_type = Metadata_mergekey_no;
271             int brief = 0;
272             int termlist = 0;
273             int rank = 0;
274             int sortkey_offset = 0;
275             
276             // now do the parsing logic
277             if (!xml_name)
278             {
279                 yaz_log(YLOG_FATAL, "Must specify name in metadata element");
280                 return 0;
281             }
282             if (xml_brief)
283             {
284                 if (!strcmp((const char *) xml_brief, "yes"))
285                     brief = 1;
286                  else if (strcmp((const char *) xml_brief, "no"))
287                 {
288                     yaz_log(YLOG_FATAL, "metadata/brief must be yes or no");
289                     return 0;
290                 }
291             }
292             else
293                 brief = 0;
294
295             if (xml_termlist)
296             {
297                 if (!strcmp((const char *) xml_termlist, "yes"))
298                     termlist = 1;
299                 else if (strcmp((const char *) xml_termlist, "no"))
300                 {
301                     yaz_log(YLOG_FATAL, "metadata/termlist must be yes or no");
302                     return 0;
303                 }
304             }
305             else
306                 termlist = 0;
307
308             if (xml_rank)
309                 rank = atoi((const char *) xml_rank);
310             else
311                 rank = 0;
312
313             if (xml_type)
314             {
315                 if (!strcmp((const char *) xml_type, "generic"))
316                     type = Metadata_type_generic;
317                 else if (!strcmp((const char *) xml_type, "year"))
318                     type = Metadata_type_year;
319                 else if (!strcmp((const char *) xml_type, "date"))
320                     type = Metadata_type_date;
321                 else
322                 {
323                     yaz_log(YLOG_FATAL, 
324                             "Unknown value for metadata/type: %s", xml_type);
325                     return 0;
326                 }
327             }
328             else
329                 type = Metadata_type_generic;
330
331             if (xml_merge)
332             {
333                 if (!strcmp((const char *) xml_merge, "no"))
334                     merge = Metadata_merge_no;
335                 else if (!strcmp((const char *) xml_merge, "unique"))
336                     merge = Metadata_merge_unique;
337                 else if (!strcmp((const char *) xml_merge, "longest"))
338                     merge = Metadata_merge_longest;
339                 else if (!strcmp((const char *) xml_merge, "range"))
340                     merge = Metadata_merge_range;
341                 else if (!strcmp((const char *) xml_merge, "all"))
342                     merge = Metadata_merge_all;
343                 else
344                 {
345                     yaz_log(YLOG_FATAL, 
346                             "Unknown value for metadata/merge: %s", xml_merge);
347                     return 0;
348                 }
349             }
350             else
351                 merge = Metadata_merge_no;
352
353             if (xml_setting)
354             {
355                 if (!strcmp((const char *) xml_setting, "no"))
356                     setting = Metadata_setting_no;
357                 else if (!strcmp((const char *) xml_setting, "postproc"))
358                     setting = Metadata_setting_postproc;
359                 else if (!strcmp((const char *) xml_setting, "parameter"))
360                     setting = Metadata_setting_parameter;
361                 else
362                 {
363                     yaz_log(YLOG_FATAL,
364                         "Unknown value for medadata/setting: %s", xml_setting);
365                     return 0;
366                 }
367             }
368
369             // add a sortkey if so specified
370             if (xml_sortkey && strcmp((const char *) xml_sortkey, "no"))
371             {
372                 if (merge == Metadata_merge_no)
373                 {
374                     yaz_log(YLOG_FATAL, 
375                             "Can't specify sortkey on a non-merged field");
376                     return 0;
377                 }
378                 if (!strcmp((const char *) xml_sortkey, "numeric"))
379                     sk_type = Metadata_sortkey_numeric;
380                 else if (!strcmp((const char *) xml_sortkey, "skiparticle"))
381                     sk_type = Metadata_sortkey_skiparticle;
382                 else
383                 {
384                     yaz_log(YLOG_FATAL,
385                             "Unknown sortkey in metadata element: %s", 
386                             xml_sortkey);
387                     return 0;
388                 }
389                 sortkey_offset = sk_node;
390
391                 conf_service_add_sortkey(
392 service, sk_node,
393                                          (const char *) xml_name, sk_type);
394                 
395                 sk_node++;
396             }
397             else
398                 sortkey_offset = -1;
399
400             if (xml_mergekey && strcmp((const char *) xml_mergekey, "no"))
401             {
402                 mergekey_type = Metadata_mergekey_yes;
403             }
404
405
406             // metadata known, assign values
407             conf_service_add_metadata(service, md_node,
408                                       (const char *) xml_name,
409                                       type, merge, setting,
410                                       brief, termlist, rank, sortkey_offset,
411                                       mergekey_type);
412
413             xmlFree(xml_name);
414             xmlFree(xml_brief);
415             xmlFree(xml_sortkey);
416             xmlFree(xml_merge);
417             xmlFree(xml_type);
418             xmlFree(xml_termlist);
419             xmlFree(xml_rank);
420             xmlFree(xml_setting);
421             md_node++;
422         }
423         else
424         {
425             yaz_log(YLOG_FATAL, "Bad element: %s", n->name);
426             return 0;
427         }
428     }
429     return service;
430 }
431
432 static char *parse_settings(NMEM nmem, xmlNode *node)
433 {
434     xmlChar *src = xmlGetProp(node, (xmlChar *) "src");
435     char *r;
436
437     if (src)
438     {
439         if (yaz_is_abspath((const char *) src))
440             r = nmem_strdup(nmem, (const char *) src);
441         else
442         {
443             r = nmem_malloc(nmem,
444                             strlen(confdir) + strlen((const char *) src) + 2);
445             sprintf(r, "%s/%s", confdir, src);
446         }
447     }
448     else
449     {
450         yaz_log(YLOG_FATAL, "Must specify src in targetprofile");
451         return 0;
452     }
453     xmlFree(src);
454     return r;
455 }
456
457 static struct conf_server *parse_server(NMEM nmem, xmlNode *node)
458 {
459     xmlNode *n;
460     struct conf_server *server = nmem_malloc(nmem, sizeof(struct conf_server));
461
462     server->host = 0;
463     server->port = 0;
464     server->proxy_host = 0;
465     server->proxy_port = 0;
466     server->myurl = 0;
467     server->service = 0;
468     server->next = 0;
469     server->server_settings = 0;
470     server->relevance_pct = 0;
471     server->sort_pct = 0;
472     server->mergekey_pct = 0;
473
474     for (n = node->children; n; n = n->next)
475     {
476         if (n->type != XML_ELEMENT_NODE)
477             continue;
478         if (!strcmp((const char *) n->name, "listen"))
479         {
480             xmlChar *port = xmlGetProp(n, (xmlChar *) "port");
481             xmlChar *host = xmlGetProp(n, (xmlChar *) "host");
482             if (port)
483                 server->port = atoi((const char *) port);
484             if (host)
485                 server->host = nmem_strdup(nmem, (const char *) host);
486             xmlFree(port);
487             xmlFree(host);
488         }
489         else if (!strcmp((const char *) n->name, "proxy"))
490         {
491             xmlChar *port = xmlGetProp(n, (xmlChar *) "port");
492             xmlChar *host = xmlGetProp(n, (xmlChar *) "host");
493             xmlChar *myurl = xmlGetProp(n, (xmlChar *) "myurl");
494             if (port)
495                 server->proxy_port = atoi((const char *) port);
496             if (host)
497                 server->proxy_host = nmem_strdup(nmem, (const char *) host);
498             if (myurl)
499                 server->myurl = nmem_strdup(nmem, (const char *) myurl);
500             xmlFree(port);
501             xmlFree(host);
502             xmlFree(myurl);
503         }
504         else if (!strcmp((const char *) n->name, "settings"))
505         {
506             if (server->server_settings)
507             {
508                 yaz_log(YLOG_FATAL, "Can't repeat 'settings'");
509                 return 0;
510             }
511             if (!(server->server_settings = parse_settings(nmem, n)))
512                 return 0;
513         }
514         else if (!strcmp((const char *) n->name, "relevance"))
515         {
516             server->relevance_pct = pp2_charset_create_xml(n);
517             if (!server->relevance_pct)
518                 return 0;
519         }
520         else if (!strcmp((const char *) n->name, "sort"))
521         {
522             server->sort_pct = pp2_charset_create_xml(n);
523             if (!server->sort_pct)
524                 return 0;
525         }
526         else if (!strcmp((const char *) n->name, "mergekey"))
527         {
528             server->mergekey_pct = pp2_charset_create_xml(n);
529             if (!server->mergekey_pct)
530                 return 0;
531         }
532         else if (!strcmp((const char *) n->name, "service"))
533         {
534             const char *service_id = (const char *)
535                 xmlGetProp(n, (xmlChar *) "id");
536
537             struct conf_service **sp = &server->service;
538             for (; *sp; sp = &(*sp)->next)
539                 if ((*sp)->id && service_id &&
540                     0 == strcmp((*sp)->id, service_id))
541                 {
542                     yaz_log(YLOG_FATAL, "Duplicate service: %s", service_id);
543                     break;
544                 }
545                 else if (!(*sp)->id && !service_id)
546                 {
547                     yaz_log(YLOG_FATAL, "Duplicate unnamed service");
548                     break;
549                 }
550
551             if (*sp)  /* service already exist */
552                 return 0;
553             else
554             {
555                 struct conf_service *s = parse_service(n, service_id);
556                 if (s)
557                     *sp = s;
558             }
559         }
560         else
561         {
562             yaz_log(YLOG_FATAL, "Bad element: %s", n->name);
563             return 0;
564         }
565     }
566     if (!server->relevance_pct)
567         server->relevance_pct = pp2_charset_create(0);
568     if (!server->sort_pct)
569         server->sort_pct = pp2_charset_create(0);
570     if (!server->mergekey_pct)
571         server->mergekey_pct = pp2_charset_create(0);
572     return server;
573 }
574
575 xsltStylesheet *conf_load_stylesheet(const char *fname)
576 {
577     char path[256];
578     if (yaz_is_abspath(fname))
579         yaz_snprintf(path, sizeof(path), fname);
580     else
581         yaz_snprintf(path, sizeof(path), "%s/%s", confdir, fname);
582     return xsltParseStylesheetFile((xmlChar *) path);
583 }
584
585 static struct conf_targetprofiles *parse_targetprofiles(NMEM nmem,
586                                                         xmlNode *node)
587 {
588     struct conf_targetprofiles *r = nmem_malloc(nmem, sizeof(*r));
589     xmlChar *type = xmlGetProp(node, (xmlChar *) "type");
590     xmlChar *src = xmlGetProp(node, (xmlChar *) "src");
591
592     memset(r, 0, sizeof(*r));
593
594     if (type)
595     {
596         if (!strcmp((const char *) type, "local"))
597             r->type = Targetprofiles_local;
598         else
599         {
600             yaz_log(YLOG_FATAL, "Unknown targetprofile type");
601             return 0;
602         }
603     }
604     else
605     {
606         yaz_log(YLOG_FATAL, "Must specify type for targetprofile");
607         return 0;
608     }
609
610     if (src)
611         r->src = nmem_strdup(nmem, (const char *) src);
612     else
613     {
614         yaz_log(YLOG_FATAL, "Must specify src in targetprofile");
615         return 0;
616     }
617     xmlFree(type);
618     xmlFree(src);
619     return r;
620 }
621
622 struct conf_service *locate_service(const char *service_id)
623 {
624     struct conf_service *s = config->servers->service;
625     for (; s; s = s->next)
626         if (s->id && service_id && 0 == strcmp(s->id, service_id))
627             return s;
628         else if (!s->id && !service_id)
629             return s;
630     return 0;
631 }
632
633
634 static struct conf_config *parse_config(xmlNode *root)
635 {
636     NMEM nmem = nmem_create();
637     xmlNode *n;
638     struct conf_config *r = nmem_malloc(nmem, sizeof(struct conf_config));
639
640     r->nmem = nmem;
641     r->servers = 0;
642     r->targetprofiles = 0;
643
644     for (n = root->children; n; n = n->next)
645     {
646         if (n->type != XML_ELEMENT_NODE)
647             continue;
648         if (!strcmp((const char *) n->name, "server"))
649         {
650             struct conf_server *tmp = parse_server(nmem, n);
651             if (!tmp)
652                 return 0;
653             tmp->next = r->servers;
654             r->servers = tmp;
655         }
656         else if (!strcmp((const char *) n->name, "targetprofiles"))
657         {
658             // It would be fun to be able to fix this sometime
659             if (r->targetprofiles)
660             {
661                 yaz_log(YLOG_FATAL, "Can't repeat targetprofiles");
662                 return 0;
663             }
664             if (!(r->targetprofiles = parse_targetprofiles(nmem, n)))
665                 return 0;
666         }
667         else
668         {
669             yaz_log(YLOG_FATAL, "Bad element: %s", n->name);
670             return 0;
671         }
672     }
673     return r;
674 }
675
676 int read_config(const char *fname)
677 {
678     xmlDoc *doc = xmlParseFile(fname);
679     const char *p;
680
681     xmlSubstituteEntitiesDefault(1);
682     xmlLoadExtDtdDefaultValue = 1;
683     if (!doc)
684     {
685         yaz_log(YLOG_FATAL, "Failed to read %s", fname);
686         exit(1);
687     }
688     if ((p = strrchr(fname, 
689 #ifdef WIN32
690                      '\\'
691 #else
692                      '/'
693 #endif
694              )))
695     {
696         int len = p - fname;
697         if (len >= sizeof(confdir))
698             len = sizeof(confdir)-1;
699         strncpy(confdir, fname, len);
700         confdir[len] = '\0';
701     }
702     config = parse_config(xmlDocGetRootElement(doc));
703     xmlFreeDoc(doc);
704
705     if (config)
706         return 1;
707     else
708         return 0;
709 }
710
711 void config_read_settings(const char *path_override)
712 {
713     struct conf_service *s = config->servers->service;
714     for (;s ; s = s->next)
715     {
716         init_settings(s);
717         if (path_override)
718             settings_read(s, path_override);
719         else if (s->settings)
720             settings_read(s, s->settings);
721         else if (config->servers->server_settings)
722             settings_read(s, config->servers->server_settings);
723         else
724             yaz_log(YLOG_WARN, "No settings for service");
725     }
726 }
727
728 /*
729  * Local variables:
730  * c-basic-offset: 4
731  * c-file-style: "Stroustrup"
732  * indent-tabs-mode: nil
733  * End:
734  * vim: shiftwidth=4 tabstop=8 expandtab
735  */
736