Moved CCL Map, record syntax, charset normalization, record syntax normalization...
[pazpar2-moved-to-github.git] / src / database.c
1 /* $Id: database.c,v 1.7 2007-04-08 20:52:09 quinn Exp $ */
2
3 #include <libxml/parser.h>
4 #include <libxml/tree.h>
5 #include <libxslt/xslt.h>
6 #include <libxslt/transform.h>
7 #include <libxslt/xsltutils.h>
8 #include <assert.h>
9 #include <sys/types.h>
10 #include <sys/stat.h>
11
12 #include "pazpar2.h"
13 #include "config.h"
14 #include "settings.h"
15 #include "http.h"
16 #include "zeerex.h"
17
18 #include <sys/types.h>
19 #include <sys/socket.h>
20 #include <netdb.h>
21 #include <netinet/in.h>
22
23 static struct host *hosts = 0;  // The hosts we know about 
24 static struct database *databases = 0; // The databases we know about
25 static NMEM nmem = 0;
26
27 static xmlDoc *get_explain_xml(const char *id)
28 {
29     struct stat st;
30     char *dir;
31     char path[256];
32     char ide[256];
33     if (!config || !config->targetprofiles)
34     {
35         yaz_log(YLOG_WARN, "Config must be loaded and specify targetprofiles");
36         return 0;
37     }
38     if (config->targetprofiles->type != Targetprofiles_local)
39     {
40         yaz_log(YLOG_FATAL, "Only supports local type");
41         return 0;
42     }
43     dir = config->targetprofiles->src;
44     urlencode(id, ide);
45     sprintf(path, "%s/%s", dir, ide);
46     if (!stat(path, &st))
47         return xmlParseFile(path);
48     else
49         return 0;
50 }
51
52 // Create a new host structure for hostport
53 static struct host *create_host(const char *hostport)
54 {
55     struct addrinfo *addrinfo, hints;
56     struct host *host;
57     char *port;
58     char ipport[128];
59     unsigned char addrbuf[4];
60     int res;
61
62     host = xmalloc(sizeof(struct host));
63     host->hostport = xstrdup(hostport);
64     host->connections = 0;
65
66     if ((port = strchr(hostport, ':')))
67         *(port++) = '\0';
68     else
69         port = "210";
70
71     hints.ai_flags = 0;
72     hints.ai_family = PF_INET;
73     hints.ai_socktype = SOCK_STREAM;
74     hints.ai_protocol = IPPROTO_TCP;
75     hints.ai_addrlen = 0;
76     hints.ai_addr = 0;
77     hints.ai_canonname = 0;
78     hints.ai_next = 0;
79     // This is not robust code. It assumes that getaddrinfo always
80     // returns AF_INET address.
81     if ((res = getaddrinfo(hostport, port, &hints, &addrinfo)))
82     {
83         yaz_log(YLOG_WARN, "Failed to resolve %s: %s", hostport, gai_strerror(res));
84         xfree(host->hostport);
85         xfree(host);
86         return 0;
87     }
88     assert(addrinfo->ai_family == PF_INET);
89     memcpy(addrbuf, &((struct sockaddr_in*)addrinfo->ai_addr)->sin_addr.s_addr, 4);
90     sprintf(ipport, "%u.%u.%u.%u:%s",
91             addrbuf[0], addrbuf[1], addrbuf[2], addrbuf[3], port);
92     host->ipport = xstrdup(ipport);
93     freeaddrinfo(addrinfo);
94     host->next = hosts;
95     hosts = host;
96     return host;
97 }
98
99 static struct host *find_host(const char *hostport)
100 {
101     struct host *p;
102     for (p = hosts; p; p = p->next)
103         if (!strcmp(p->hostport, hostport))
104             return p;
105     return create_host(hostport);
106 }
107
108 static struct database *load_database(const char *id)
109 {
110     xmlDoc *doc = get_explain_xml(id);
111     struct zr_explain *explain = 0;
112     struct database *db;
113     struct host *host;
114     char hostport[256];
115     char *dbname;
116
117     if (!nmem)
118         nmem = nmem_create();
119     if (doc)
120     {
121         explain = zr_read_xml(nmem, xmlDocGetRootElement(doc));
122         if (!explain)
123             return 0;
124     }
125     if (strlen(id) > 255)
126         return 0;
127     strcpy(hostport, id);
128     if ((dbname = strchr(hostport, '/')))
129         *(dbname++) = '\0';
130     else
131         dbname = "Default";
132     if (!(host = find_host(hostport)))
133         return 0;
134     db = nmem_malloc(nmem, sizeof(*db));
135     memset(db, 0, sizeof(*db));
136     db->host = host;
137     db->url = nmem_strdup(nmem, id);
138     db->name = 0;
139     db->databases = xmalloc(2 * sizeof(char *));
140     db->databases[0] = nmem_strdup(nmem, dbname);
141     db->databases[1] = 0;
142     db->errors = 0;
143     db->explain = explain;
144     db->settings = 0;
145     db->next = databases;
146     db->ccl_map = 0;
147     db->yaz_marc = 0;
148     db->map = 0;
149     databases = db;
150
151     return db;
152 }
153
154 // Return a database structure by ID. Load and add to list if necessary
155 // new==1 just means we know it's not in the list
156 struct database *find_database(const char *id, int new)
157 {
158     struct database *p;
159     if (!new)
160     {
161         for (p = databases; p; p = p->next)
162             if (!strcmp(p->url, id))
163                 return p;
164     }
165     return load_database(id);
166 }
167
168 static int match_zurl(const char *zurl, const char *pattern)
169 {
170     if (!strcmp(pattern, "*"))
171         return 1;
172     else if (!strncmp(pattern, "*/", 2))
173     {
174         char *db = strchr(zurl, '/');
175         if (!db)
176             return 0;
177         if (!strcmp(pattern + 2, db))
178             return 1;
179         else
180             return 0;
181     }
182     else if (!strcmp(pattern, zurl))
183         return 1;
184     else
185         return 0;
186 }
187
188 // This will be generalized at some point
189 static int match_criterion(struct database *db, struct database_criterion *c)
190 {
191     if (!strcmp(c->name, "id"))
192     {
193         struct database_criterion_value *v;
194         for (v = c->values; v; v = v->next)
195             if (match_zurl(db->url, v->value))
196                 return 1;
197         return 0;
198     }
199     else
200         return 0;
201 }
202
203 int database_match_criteria(struct database *db, struct database_criterion *cl)
204 {
205     for (; cl; cl = cl->next)
206         if (!match_criterion(db, cl))
207             break;
208     if (cl) // one of the criteria failed to match -- skip this db
209         return 0;
210     else
211         return 1;
212 }
213
214 // Cycles through databases, calling a handler function on the ones for
215 // which all criteria matched.
216 int grep_databases(void *context, struct database_criterion *cl,
217         void (*fun)(void *context, struct database *db))
218 {
219     struct database *p;
220     int i;
221
222     for (p = databases; p; p = p->next)
223     {
224         if (database_match_criteria(p, cl))
225         {
226             (*fun)(context, p);
227             i++;
228         }
229     }
230     return i;
231 }
232
233 // Initialize CCL map for a target
234 // Note: This approach ignores user-specific CCL maps, for which I
235 // don't presently see any application.
236 static void prepare_cclmap(void *ignore, struct database *db)
237 {
238     struct setting *s;
239
240     if (!db->settings)
241         return;
242     db->ccl_map = ccl_qual_mk();
243     for (s = db->settings[PZ_CCLMAP]; s; s = s->next)
244         if (!*s->user)
245         {
246             char *p = strchr(s->name + 3, ':');
247             if (!p)
248             {
249                 yaz_log(YLOG_FATAL, "Malformed cclmap name: %s", s->name);
250                 exit(1);
251             }
252             p++;
253             ccl_qual_fitem(db->ccl_map, s->value, p);
254         }
255 }
256
257 // Initialize YAZ Map structures for MARC-based targets
258 static void prepare_yazmarc(void *ignore, struct database *db)
259 {
260     struct setting *s;
261
262     if (!db->settings)
263         return;
264     for (s = db->settings[PZ_NATIVESYNTAX]; s; s = s->next)
265         if (!*s->user && !strcmp(s->value, "iso2709"))
266         {
267             char *encoding = "marc-8s";
268             yaz_iconv_t cm;
269
270             db->yaz_marc = yaz_marc_create();
271             yaz_marc_subfield_str(db->yaz_marc, "\t");
272             // See if a native encoding is specified
273             for (s = db->settings[PZ_ENCODING]; s; s = s->next)
274                 if (!*s->user)
275                 {
276                     encoding = s->value;
277                     break;
278                 }
279             if (!(cm = yaz_iconv_open("utf-8", encoding)))
280             {
281                 yaz_log(YLOG_FATAL, "Unable to map from %s to UTF-8", encoding);
282                 exit(1);
283             }
284             yaz_marc_iconv(db->yaz_marc, cm);
285             break;
286         }
287 }
288
289 // Prepare XSLT stylesheets for record normalization
290 static void prepare_map(void *ignore, struct database *db)
291 {
292     struct setting *s;
293
294     if (!db->settings)
295         return;
296     for (s = db->settings[PZ_XSLT]; s; s = s->next)
297         if (!*s->user)
298         {
299             char **stylesheets;
300             struct database_retrievalmap **m = &db->map;
301             int num, i;
302
303             nmem_strsplit(nmem, ",", s->value, &stylesheets, &num);
304             for (i = 0; i < num; i++)
305             {
306                 (*m) = nmem_malloc(nmem, sizeof(**m));
307                 (*m)->next = 0;
308                 if (!((*m)->stylesheet = conf_load_stylesheet(stylesheets[i])))
309                 {
310                     yaz_log(YLOG_FATAL, "Unable to load stylesheet: %s",
311                             stylesheets[i]);
312                     exit(1);
313                 }
314                 m = &(*m)->next;
315             }
316             break;
317         }
318     if (!s)
319         yaz_log(YLOG_WARN, "No Normalization stylesheet for target %s", db->url);
320 }
321
322 // Read settings for each database, and prepare support data structures
323 void prepare_databases(void)
324 {
325     grep_databases(0, 0, prepare_cclmap);
326     grep_databases(0, 0, prepare_yazmarc);
327     grep_databases(0, 0, prepare_map);
328 }
329
330 // This function will most likely vanish when a proper target profile mechanism is
331 // introduced.
332 void load_simpletargets(const char *fn)
333 {
334     FILE *f = fopen(fn, "r");
335     char line[256];
336
337     if (!f)
338     {
339         yaz_log(YLOG_WARN|YLOG_ERRNO, "open %s", fn);
340         exit(1);
341     }
342
343     while (fgets(line, 255, f))
344     {
345         char *url;
346         char *name;
347         struct database *db;
348
349         if (strncmp(line, "target ", 7))
350             continue;
351         line[strlen(line) - 1] = '\0';
352
353         if ((name = strchr(line, ';')))
354             *(name++) = '\0';
355
356         url = line + 7;
357
358         if (!(db = find_database(url, 0)))
359             yaz_log(YLOG_WARN, "Unable to load database %s", url);
360         if (name && db)
361             db->name = nmem_strdup(nmem, name);
362     }
363     fclose(f);
364 }
365
366
367 /*
368  * Local variables:
369  * c-basic-offset: 4
370  * indent-tabs-mode: nil
371  * End:
372  * vim: shiftwidth=4 tabstop=8 expandtab
373  */