session.[ch] replaces logic.c, pazpar2.h
[pazpar2-moved-to-github.git] / src / database.c
1 /* This file is part of Pazpar2.
2    Copyright (C) 2006-2010 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 <libxml/parser.h>
25 #include <libxml/tree.h>
26 #include <assert.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <yaz/log.h>
30 #include <yaz/nmem.h>
31
32 #include "session.h"
33 #include "host.h"
34 #include "pazpar2_config.h"
35 #include "settings.h"
36 #include "http.h"
37 #include "zeerex.h"
38 #include "database.h"
39
40 #include <sys/types.h>
41 #if HAVE_SYS_SOCKET_H
42 #include <sys/socket.h>
43 #endif
44 #if HAVE_NETDB_H
45 #include <netdb.h>
46 #endif
47 #if HAVE_NETINET_IN_H
48 #include <netinet/in.h>
49 #endif
50
51 enum pazpar2_database_criterion_type {
52     PAZPAR2_STRING_MATCH,
53     PAZPAR2_SUBSTRING_MATCH
54 };
55
56 struct database_criterion_value {
57     char *value;
58     struct database_criterion_value *next;
59 };
60
61 struct database_criterion {
62     char *name;
63     enum pazpar2_database_criterion_type type;
64     struct database_criterion_value *values;
65     struct database_criterion *next;
66 };
67
68
69 struct database_hosts {
70     struct host *hosts;
71     YAZ_MUTEX mutex;
72 };
73
74 static xmlDoc *get_explain_xml(struct conf_targetprofiles *targetprofiles,
75                                const char *id)
76 {
77     struct stat st;
78     char *dir;
79     char path[256];
80     char ide[256];
81     if (targetprofiles->type != Targetprofiles_local)
82     {
83         yaz_log(YLOG_FATAL, "Only supports local type");
84         return 0;
85     }
86     dir = targetprofiles->src;
87     urlencode(id, ide);
88     sprintf(path, "%s/%s", dir, ide);
89     if (!stat(path, &st))
90         return xmlParseFile(path);
91     else
92         return 0;
93 }
94
95 // Create a new host structure for hostport
96 static struct host *create_host(const char *hostport, iochan_man_t iochan_man)
97 {
98     struct host *host;
99
100     host = xmalloc(sizeof(struct host));
101     host->hostport = xstrdup(hostport);
102     host->connections = 0;
103     host->ipport = 0;
104     host->mutex = 0;
105
106     if (host_getaddrinfo(host, iochan_man))
107     {
108         xfree(host->hostport);
109         xfree(host);
110         return 0;
111     }
112     yaz_mutex_create(&host->mutex);
113
114     return host;
115 }
116
117 static struct host *find_host(database_hosts_t hosts,
118                               const char *hostport, iochan_man_t iochan_man)
119 {
120     struct host *p;
121     yaz_mutex_enter(hosts->mutex);
122     for (p = hosts->hosts; p; p = p->next)
123         if (!strcmp(p->hostport, hostport))
124             break;
125     if (!p)
126     {
127         p = create_host(hostport, iochan_man);
128         if (p)
129         {
130             p->next = hosts->hosts;
131             hosts->hosts = p;
132         }
133     }
134     yaz_mutex_leave(hosts->mutex);
135     return p;
136 }
137
138 int resolve_database(struct conf_service *service, struct database *db)
139 {
140     if (db->host == 0)
141     {
142         struct host *host;
143         char *p;
144         char hostport[256];
145         strcpy(hostport, db->url);
146         if ((p = strchr(hostport, '/')))
147             *p = '\0';
148         if (!(host = find_host(service->server->database_hosts,
149                                hostport, service->server->iochan_man)))
150             return -1;
151         db->host = host;
152     }
153     return 0;
154 }
155
156 void resolve_databases(struct conf_service *service)
157 {
158     struct database *db = service->databases;
159     for (; db; db = db->next)
160         resolve_database(service, db);
161 }
162
163 struct database *new_database(const char *id, NMEM nmem)
164 {
165     struct database *db;
166     char hostport[256];
167     char *dbname;
168     struct setting *idset;
169
170     if (strlen(id) > 255)
171         return 0;
172     strcpy(hostport, id);
173     if ((dbname = strchr(hostport, '/')))
174         *(dbname++) = '\0';
175     else
176         dbname = "";
177     db = nmem_malloc(nmem, sizeof(*db));
178     memset(db, 0, sizeof(*db));
179     db->host = 0;
180     db->url = nmem_strdup(nmem, id);
181     db->databases = nmem_malloc(nmem, 2 * sizeof(char *));
182     db->databases[0] = nmem_strdup(nmem, dbname);
183     db->databases[1] = 0;
184     db->errors = 0;
185     db->explain = 0;
186
187     db->num_settings = PZ_NEGOTIATION_CHARSET+1;
188     db->settings = nmem_malloc(nmem, sizeof(struct settings*) * 
189                                db->num_settings);
190     memset(db->settings, 0, sizeof(struct settings*) * db->num_settings);
191     idset = nmem_malloc(nmem, sizeof(*idset));
192     idset->precedence = 0;
193     idset->name = "pz:id";
194     idset->target = idset->value = db->url;
195     idset->next = 0;
196     db->settings[PZ_ID] = idset;
197     db->next = 0;
198
199     return db;
200 }
201
202 static struct database *load_database(const char *id,
203                                       struct conf_service *service)
204 {
205     struct database *db;
206     struct zr_explain *explain = 0;
207     xmlDoc *doc = 0;
208
209     if (service->targetprofiles 
210         && (doc = get_explain_xml(service->targetprofiles, id)))
211     {
212         explain = zr_read_xml(service->nmem, xmlDocGetRootElement(doc));
213         if (!explain)
214             return 0;
215     }
216     db = new_database(id, service->nmem);
217     db->explain = explain;
218     db->next = service->databases;
219     service->databases = db;
220
221     return db;
222 }
223
224 // Return a database structure by ID. Load and add to list if necessary
225 // new==1 just means we know it's not in the list
226 struct database *find_database(const char *id, struct conf_service *service)
227 {
228     struct database *p;
229     for (p = service->databases; p; p = p->next)
230         if (!strcmp(p->url, id))
231             return p;
232     return load_database(id, service);
233 }
234
235 // This whole session_grep database thing should be moved elsewhere
236
237 int match_zurl(const char *zurl, const char *pattern)
238 {
239     int len;
240
241     if (!strcmp(pattern, "*"))
242         return 1;
243     else if (!strncmp(pattern, "*/", 2))   // host wildcard.. what the heck is that for?
244     {
245         char *db = strchr(zurl, '/');
246         if (!db)
247             return 0;
248         if (!strcmp(pattern + 2, db))
249             return 1;
250         else
251             return 0;
252     }
253     else if (*(pattern + (len = strlen(pattern) - 1)) == '*')  // db wildcard
254     {
255         if (!strncmp(pattern, zurl, len))
256             return 1;
257         else
258             return 2;
259     }
260     else if (!strcmp(pattern, zurl))
261         return 1;
262     else
263         return 0;
264 }
265
266 // This will be generalized at some point
267 static int match_criterion(struct setting **settings,
268                            struct conf_service *service, 
269                            struct database_criterion *c)
270 {
271     int offset = settings_lookup_offset(service, c->name);
272     struct database_criterion_value *v;
273
274     if (offset < 0)
275     {
276         yaz_log(YLOG_WARN, "Criterion not found: %s", c->name);
277         return 0;
278     }
279     if (!settings[offset])
280         return 0;
281     for (v = c->values; v; v = v->next)
282     {
283         if (c->type == PAZPAR2_STRING_MATCH)
284         {
285             if (offset == PZ_ID)
286             {
287                 if (match_zurl(settings[offset]->value, v->value))
288                     break;
289             }
290             else 
291             {
292                 if (!strcmp(settings[offset]->value, v->value))
293                     break;
294             }
295         }            
296         else if (c->type == PAZPAR2_SUBSTRING_MATCH)
297         {
298             if (strstr(settings[offset]->value, v->value))
299                 break;
300         }
301     }
302     if (v)
303         return 1;
304     else
305         return 0;
306 }
307
308 // parses crit1=val1,crit2=val2|val3,...
309 static struct database_criterion *create_database_criterion(NMEM m,
310                                                             const char *buf)
311 {
312     struct database_criterion *res = 0;
313     char **values;
314     int num;
315     int i;
316
317     if (!buf || !*buf)
318         return 0;
319     nmem_strsplit(m, ",", buf,  &values, &num);
320     for (i = 0; i < num; i++)
321     {
322         char **subvalues;
323         int subnum;
324         int subi;
325         struct database_criterion *new = nmem_malloc(m, sizeof(*new));
326         char *eq;
327         if ((eq = strchr(values[i], '=')))
328             new->type = PAZPAR2_STRING_MATCH;
329         else if ((eq = strchr(values[i], '~')))
330             new->type = PAZPAR2_SUBSTRING_MATCH;
331         else
332         {
333             yaz_log(YLOG_WARN, "Missing equal-sign/tilde in filter");
334             return 0;
335         }
336         *(eq++) = '\0';
337         new->name = values[i];
338         nmem_strsplit(m, "|", eq, &subvalues, &subnum);
339         new->values = 0;
340         for (subi = 0; subi < subnum; subi++)
341         {
342             struct database_criterion_value *newv
343                 = nmem_malloc(m, sizeof(*newv));
344             newv->value = subvalues[subi];
345             newv->next = new->values;
346             new->values = newv;
347         }
348         new->next = res;
349         res = new;
350     }
351     return res;
352 }
353
354 static int database_match_criteria(struct setting **settings,
355                                    struct conf_service *service,
356                                    struct database_criterion *cl)
357 {
358     for (; cl; cl = cl->next)
359         if (!match_criterion(settings, service, cl))
360             break;
361     if (cl) // one of the criteria failed to match -- skip this db
362         return 0;
363     else
364         return 1;
365 }
366
367 // Cycles through databases, calling a handler function on the ones for
368 // which all criteria matched.
369 int session_grep_databases(struct session *se, const char *filter,
370                            void (*fun)(void *context, struct session_database *db))
371 {
372     struct session_database *p;
373     NMEM nmem = nmem_create();
374     int i = 0;
375     struct database_criterion *cl = create_database_criterion(nmem, filter);
376
377     for (p = se->databases; p; p = p->next)
378     {
379         if (p->settings && p->settings[PZ_ALLOW] && *p->settings[PZ_ALLOW]->value == '0')
380             continue;
381         if (!p->settings[PZ_NAME])
382             continue;
383         if (database_match_criteria(p->settings, se->service, cl))
384         {
385             (*fun)(se, p);
386             i++;
387         }
388     }
389     nmem_destroy(nmem);
390     return i;
391 }
392
393 int predef_grep_databases(void *context, struct conf_service *service,
394                           void (*fun)(void *context, struct database *db))
395 {
396     struct database *p;
397     int i = 0;
398
399     for (p = service->databases; p; p = p->next)
400         if (database_match_criteria(p->settings, service, 0))
401         {
402             (*fun)(context, p);
403             i++;
404         }
405     return i;
406 }
407
408 database_hosts_t database_hosts_create(void)
409 {
410     database_hosts_t p = xmalloc(sizeof(*p));
411     p->hosts = 0;
412     p->mutex = 0;
413     yaz_mutex_create(&p->mutex);
414     return p;
415 }
416
417 /*
418  * Local variables:
419  * c-basic-offset: 4
420  * c-file-style: "Stroustrup"
421  * indent-tabs-mode: nil
422  * End:
423  * vim: shiftwidth=4 tabstop=8 expandtab
424  */
425