Perform getaddrinfo in separate thread for a given struct host.
[pazpar2-moved-to-github.git] / src / logic.c
index 5632307..12500ff 100644 (file)
@@ -1,4 +1,4 @@
-/* $Id: logic.c,v 1.6 2007-04-18 04:07:48 quinn Exp $
+/* $Id: logic.c,v 1.16 2007-04-21 12:00:54 adam Exp $
    Copyright (c) 2006-2007, Index Data.
 
 This file is part of Pazpar2.
@@ -19,6 +19,9 @@ Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
 02111-1307, USA.
  */
 
+// This file contains the primary business logic. Several parts of it should
+// Eventually be factored into separate modules.
+
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
@@ -74,8 +77,6 @@ static int client_prep_connection(struct client *cl);
 static void ingest_records(struct client *cl, Z_Records *r);
 void session_alert_watch(struct session *s, int what);
 
-IOCHAN channel_list = 0;  // Master list of connections we're handling events to
-
 static struct connection *connection_freelist = 0;
 static struct client *client_freelist = 0;
 
@@ -103,7 +104,7 @@ struct parameters global_parameters =
     0,
     30,
     "81",
-    "Index Data PazPar2 (MasterKey)",
+    "Index Data PazPar2",
     VERSION,
     600, // 10 minutes
     60,
@@ -206,8 +207,13 @@ static void send_init(IOCHAN i)
     odr_reset(global_parameters.odr_out);
 }
 
+// Recursively traverse query structure to extract terms.
 static void pull_terms(NMEM nmem, struct ccl_rpn_node *n, char **termlist, int *num)
 {
+    char **words;
+    int numwords;
+    int i;
+
     switch (n->kind)
     {
         case CCL_RPN_AND:
@@ -218,7 +224,9 @@ static void pull_terms(NMEM nmem, struct ccl_rpn_node *n, char **termlist, int *
             pull_terms(nmem, n->u.p[1], termlist, num);
             break;
         case CCL_RPN_TERM:
-            termlist[(*num)++] = nmem_strdup(nmem, n->u.t.term);
+            nmem_strsplit(nmem, " ", n->u.t.term, &words, &numwords);
+            for (i = 0; i < numwords; i++)
+                termlist[(*num)++] = words[i];
             break;
         default: // NOOP
             break;
@@ -513,36 +521,36 @@ static void add_facet(struct session *s, const char *type, const char *value)
 static xmlDoc *normalize_record(struct client *cl, Z_External *rec)
 {
     struct database_retrievalmap *m;
-    struct database *db = cl->database->database;
+    struct session_database *sdb = cl->database;
+    struct database *db = sdb->database;
     xmlNode *res;
     xmlDoc *rdoc;
 
     // First normalize to XML
-    if (db->yaz_marc)
+    if (sdb->yaz_marc)
     {
         char *buf;
         int len;
         if (rec->which != Z_External_octet)
         {
             yaz_log(YLOG_WARN, "Unexpected external branch, probably BER %s",
-                    cl->database->database->url);
+                    db->url);
             return 0;
         }
         buf = (char*) rec->u.octet_aligned->buf;
         len = rec->u.octet_aligned->len;
-        if (yaz_marc_read_iso2709(db->yaz_marc, buf, len) < 0)
+        if (yaz_marc_read_iso2709(sdb->yaz_marc, buf, len) < 0)
         {
-            yaz_log(YLOG_WARN, "Failed to decode MARC %s",
-                    cl->database->database->url);
+            yaz_log(YLOG_WARN, "Failed to decode MARC %s", db->url);
             return 0;
         }
 
-        yaz_marc_write_using_libxml2(db->yaz_marc, 1);
-        if (yaz_marc_write_xml(db->yaz_marc, &res,
+        yaz_marc_write_using_libxml2(sdb->yaz_marc, 1);
+        if (yaz_marc_write_xml(sdb->yaz_marc, &res,
                     "http://www.loc.gov/MARC21/slim", 0, 0) < 0)
         {
             yaz_log(YLOG_WARN, "Failed to encode as XML %s",
-                    cl->database->database->url);
+                    db->url);
             return 0;
         }
         rdoc = xmlNewDoc((xmlChar *) "1.0");
@@ -553,14 +561,14 @@ static xmlDoc *normalize_record(struct client *cl, Z_External *rec)
     {
         yaz_log(YLOG_FATAL, 
                 "Unknown native_syntax in normalize_record from %s",
-                cl->database->database->url);
+                db->url);
         exit(1);
     }
 
     if (global_parameters.dump_records){
         fprintf(stderr, 
                 "Input Record (normalized) from %s\n----------------\n",
-                cl->database->database->url);
+                db->url);
 #if LIBXML_VERSION >= 20600
         xmlDocFormatDump(stderr, rdoc, 1);
 #else
@@ -568,7 +576,7 @@ static xmlDoc *normalize_record(struct client *cl, Z_External *rec)
 #endif
     }
 
-    for (m = db->map; m; m = m->next){
+    for (m = sdb->map; m; m = m->next){
         xmlDoc *new = 0;
 
 #if 1
@@ -959,7 +967,7 @@ static void do_presentResponse(IOCHAN i, Z_APDU *a)
     }
 }
 
-static void handler(IOCHAN i, int event)
+void connection_handler(IOCHAN i, int event)
 {
     struct connection *co = iochan_getdata(i);
     struct client *cl = co->client;
@@ -1066,7 +1074,7 @@ static void handler(IOCHAN i, int event)
 
     if (cl->state == Client_Idle)
     {
-        if (cl->requestid != se->requestid && *se->query) {
+        if (cl->requestid != se->requestid && cl->pquery) {
             send_search(i);
         }
         else if (cl->hits > 0 && cl->records < global_parameters.toget &&
@@ -1118,6 +1126,7 @@ static void connection_destroy(struct connection *co)
     connection_freelist = co;
 }
 
+
 // Creates a new connection for client, associated with the host of 
 // client's database
 static struct connection *connection_create(struct client *cl)
@@ -1128,6 +1137,13 @@ static struct connection *connection_create(struct client *cl)
     void *addr;
 
 
+    if (!cl->database->database->host->ipport)
+    {
+        yaz_log(YLOG_WARN, "Not yet resolved: %s",
+                cl->database->database->url);
+        return 0;
+    }
+
     if (!(link = cs_create(tcpip_type, 0, PROTO_Z3950)))
         {
             yaz_log(YLOG_FATAL|YLOG_ERRNO, "Failed to create comstack");
@@ -1183,10 +1199,9 @@ static struct connection *connection_create(struct client *cl)
     cl->connection = new;
     new->link = link;
 
-    new->iochan = iochan_create(cs_fileno(link), handler, 0);
+    new->iochan = iochan_create(cs_fileno(link), connection_handler, 0);
     iochan_setdata(new->iochan, new);
-    new->iochan->next = channel_list;
-    channel_list = new->iochan;
+    pazpar2_add_channel(new->iochan);
     return new;
 }
 
@@ -1244,14 +1259,140 @@ static int client_prep_connection(struct client *cl)
         return 0;
 }
 
+// Initialize YAZ Map structures for MARC-based targets
+static int prepare_yazmarc(struct session_database *sdb)
+{
+    char *s;
+
+    if (!sdb->settings)
+    {
+        yaz_log(YLOG_WARN, "No settings for %s", sdb->database->url);
+        return -1;
+    }
+    if ((s = session_setting_oneval(sdb, PZ_NATIVESYNTAX)) && !strncmp(s, "iso2709", 7))
+    {
+        char *encoding = "marc-8s", *e;
+        yaz_iconv_t cm;
+
+        // See if a native encoding is specified
+        if ((e = strchr(s, ';')))
+            encoding = e + 1;
+
+        sdb->yaz_marc = yaz_marc_create();
+        yaz_marc_subfield_str(sdb->yaz_marc, "\t");
+        
+        cm = yaz_iconv_open("utf-8", encoding);
+        if (!cm)
+        {
+            yaz_log(YLOG_FATAL, 
+                    "Unable to map from %s to UTF-8 for target %s", 
+                    encoding, sdb->database->url);
+            return -1;
+        }
+        yaz_marc_iconv(sdb->yaz_marc, cm);
+    }
+    return 0;
+}
+
+// Prepare XSLT stylesheets for record normalization
+// Structures are allocated on the session_wide nmem to avoid having
+// to recompute this for every search. This would lead
+// to leaking if a single session was to repeatedly change the PZ_XSLT
+// setting. However, this is not a realistic use scenario.
+static int prepare_map(struct session *se, struct session_database *sdb)
+{
+   char *s;
+
+    if (!sdb->settings)
+    {
+        yaz_log(YLOG_WARN, "No settings on %s", sdb->database->url);
+        return -1;
+    }
+    if ((s = session_setting_oneval(sdb, PZ_XSLT)))
+    {
+        char **stylesheets;
+        struct database_retrievalmap **m = &sdb->map;
+        int num, i;
+
+        nmem_strsplit(se->session_nmem, ",", s, &stylesheets, &num);
+        for (i = 0; i < num; i++)
+        {
+            (*m) = nmem_malloc(se->session_nmem, sizeof(**m));
+            (*m)->next = 0;
+            if (!((*m)->stylesheet = conf_load_stylesheet(stylesheets[i])))
+            {
+                yaz_log(YLOG_FATAL, "Unable to load stylesheet: %s",
+                        stylesheets[i]);
+                return -1;
+            }
+            m = &(*m)->next;
+        }
+    }
+    if (!sdb->map)
+        yaz_log(YLOG_WARN, "No Normalization stylesheet for target %s",
+                sdb->database->url);
+    return 0;
+}
+
+// This analyzes settings and recomputes any supporting data structures
+// if necessary.
+static int prepare_session_database(struct session *se, struct session_database *sdb)
+{
+    if (!sdb->settings)
+    {
+        yaz_log(YLOG_WARN, "No settings associated with %s", sdb->database->url);
+        return -1;
+    }
+    if (sdb->settings[PZ_NATIVESYNTAX] && !sdb->yaz_marc)
+    {
+        if (prepare_yazmarc(sdb) < 0)
+            return -1;
+    }
+    if (sdb->settings[PZ_XSLT] && !sdb->map)
+    {
+        if (prepare_map(se, sdb) < 0)
+            return -1;
+    }
+    return 0;
+}
+
+// Initialize CCL map for a target
+static CCL_bibset prepare_cclmap(struct client *cl)
+{
+    struct session_database *sdb = cl->database;
+    struct setting *s;
+    CCL_bibset res;
+
+    if (!sdb->settings)
+        return 0;
+    res = ccl_qual_mk();
+    for (s = sdb->settings[PZ_CCLMAP]; s; s = s->next)
+    {
+        char *p = strchr(s->name + 3, ':');
+        if (!p)
+        {
+            yaz_log(YLOG_WARN, "Malformed cclmap name: %s", s->name);
+            ccl_qual_rm(&res);
+            return 0;
+        }
+        p++;
+        ccl_qual_fitem(res, s->value, p);
+    }
+    return res;
+}
+
 // Parse the query given the settings specific to this client
-static int client_parse_query(struct client *cl)
+static int client_parse_query(struct client *cl, const char *query)
 {
     struct session *se = cl->session;
     struct ccl_rpn_node *cn;
     int cerror, cpos;
+    CCL_bibset ccl_map = prepare_cclmap(cl);
 
-    cn = ccl_find_str(cl->database->database->ccl_map, se->query, &cerror, &cpos);
+    if (!ccl_map)
+        return -1;
+    cn = ccl_find_str(ccl_map, query, &cerror, &cpos);
+    ccl_qual_rm(&ccl_map);
     if (!cn)
     {
         cl->state = Client_Error;
@@ -1423,7 +1564,6 @@ char *search(struct session *se, char *query, char *filter)
 
     nmem_reset(se->nmem);
     criteria = parse_filter(se->nmem, filter);
-    strcpy(se->query, query);
     se->requestid++;
     live_channels = select_targets(se, criteria);
     if (live_channels)
@@ -1439,13 +1579,18 @@ char *search(struct session *se, char *query, char *filter)
         return "NOTARGETS";
 
     se->relevance = 0;
+
     for (cl = se->clients; cl; cl = cl->next)
     {
-        if (client_prep_connection(cl))
-        {
-            if (client_parse_query(cl) < 0)  // Query must parse for all targets
-                return "QUERY";
-        }
+        if (prepare_session_database(se, cl->database) < 0)
+            return "CONFIG_ERROR";
+        if (client_parse_query(cl, query) < 0)  // Query must parse for all targets
+            return "QUERY";
+    }
+
+    for (cl = se->clients; cl; cl = cl->next)
+    {
+        client_prep_connection(cl);
     }
 
     return 0;
@@ -1473,6 +1618,29 @@ void session_apply_setting(struct session *se, char *dbname, char *setting, char
             new->value = value;
             new->next = sdb->settings[offset];
             sdb->settings[offset] = new;
+
+            // Force later recompute of settings-driven data structures
+            // (happens when a search starts and client connections are prepared)
+            switch (offset)
+            {
+                case PZ_NATIVESYNTAX:
+                    if (sdb->yaz_marc)
+                    {
+                        yaz_marc_destroy(sdb->yaz_marc);
+                        sdb->yaz_marc = 0;
+                    }
+                    break;
+                case PZ_XSLT:
+                    if (sdb->map)
+                    {
+                        struct database_retrievalmap *m;
+                        // We don't worry about the map structure -- it's in nmem
+                        for (m = sdb->map; m; m = m->next)
+                            xsltFreeStylesheet(m->stylesheet);
+                        sdb->map = 0;
+                    }
+                    break;
+            }
             break;
         }
     if (!sdb)
@@ -1487,6 +1655,8 @@ void session_init_databases_fun(void *context, struct database *db)
     int i;
 
     new->database = db;
+    new->yaz_marc = 0;
+    new->map = 0;
     new->settings = nmem_malloc(se->session_nmem, sizeof(struct settings *) * num);
     for (i = 0; i < num; i++)
         new->settings[i] = db->settings[i];
@@ -1525,7 +1695,6 @@ struct session *new_session(NMEM nmem)
     session->requestid = -1;
     session->clients = 0;
     session->expected_maxrecs = 0;
-    session->query[0] = '\0';
     session->session_nmem = nmem;
     session->nmem = nmem_create();
     session->wrbuf = wrbuf_alloc();
@@ -1751,7 +1920,18 @@ void start_zproxy(void)
         return;
 }
 
+// Master list of connections we're handling events to
+static IOCHAN channel_list = 0; 
+void pazpar2_add_channel(IOCHAN chan)
+{
+    chan->next = channel_list;
+    channel_list = chan;
+}
 
+void pazpar2_event_loop()
+{
+    event_loop(&channel_list);
+}
 
 /*
  * Local variables: