Connections now wait for hosts to be resolved. Delayed connect attempt
[pazpar2-moved-to-github.git] / src / logic.c
index 3cc1a23..bce0ad1 100644 (file)
@@ -1,4 +1,4 @@
-/* $Id: logic.c,v 1.12 2007-04-20 04:32:33 quinn Exp $
+/* $Id: logic.c,v 1.18 2007-04-22 16:41:42 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>
@@ -518,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");
@@ -558,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
@@ -573,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
@@ -964,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;
@@ -1097,8 +1100,12 @@ static void connection_release(struct connection *co)
 static void connection_destroy(struct connection *co)
 {
     struct host *h = co->host;
-    cs_close(co->link);
-    iochan_destroy(co->iochan);
+    
+    if (co->link)
+    {
+        cs_close(co->link);
+        iochan_destroy(co->iochan);
+    }
 
     yaz_log(YLOG_DEBUG, "Connection destroy %s", co->host->hostport);
     if (h->connections == co)
@@ -1123,54 +1130,103 @@ 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)
+static int connection_connect(struct connection *con)
 {
-    struct connection *new;
-    COMSTACK link; 
-    int res;
+    COMSTACK link = 0;
+    struct client *cl = con->client;
+    struct host *host = con->host;
     void *addr;
+    int res;
 
+    assert(host->ipport);
+    assert(cl);
 
     if (!(link = cs_create(tcpip_type, 0, PROTO_Z3950)))
-        {
-            yaz_log(YLOG_FATAL|YLOG_ERRNO, "Failed to create comstack");
-            exit(1);
-        }
+    {
+        yaz_log(YLOG_FATAL|YLOG_ERRNO, "Failed to create comstack");
+        exit(1);
+    }
     
     if (0 == strlen(global_parameters.zproxy_override)){
         /* no Z39.50 proxy needed - direct connect */
         yaz_log(YLOG_DEBUG, "Connection create %s", cl->database->database->url);
         
-        if (!(addr = cs_straddr(link, cl->database->database->host->ipport)))
-            {
-                yaz_log(YLOG_WARN|YLOG_ERRNO, 
-                        "Lookup of IP address %s failed", 
-                        cl->database->database->host->ipport);
-                return 0;
-            }
-    
+        if (!(addr = cs_straddr(link, host->ipport)))
+        {
+            yaz_log(YLOG_WARN|YLOG_ERRNO, 
+                    "Lookup of IP address %s failed", host->ipport);
+            return -1;
+        }
+        
     } else {
         /* Z39.50 proxy connect */
         yaz_log(YLOG_DEBUG, "Connection create %s proxy %s", 
                 cl->database->database->url, global_parameters.zproxy_override);
-
+        
         if (!(addr = cs_straddr(link, global_parameters.zproxy_override)))
-            {
-                yaz_log(YLOG_WARN|YLOG_ERRNO, 
-                        "Lookup of IP address %s failed", 
-                        global_parameters.zproxy_override);
-                return 0;
-            }
+        {
+            yaz_log(YLOG_WARN|YLOG_ERRNO, 
+                    "Lookup of IP address %s failed", 
+                    global_parameters.zproxy_override);
+            return -1;
+        }
     }
-
+    
     res = cs_connect(link, addr);
     if (res < 0)
     {
         yaz_log(YLOG_WARN|YLOG_ERRNO, "cs_connect %s", cl->database->database->url);
-        return 0;
+        return -1;
+    }
+    con->link = link;
+    con->state = Conn_Connecting;
+    con->iochan = iochan_create(cs_fileno(link), connection_handler, 0);
+    iochan_setdata(con->iochan, con);
+    pazpar2_add_channel(con->iochan);
+
+    /* this fragment is bad DRY: from client_prep_connection */
+    cl->state = Client_Connecting;
+    iochan_setflag(con->iochan, EVENT_OUTPUT);
+    return 0;
+}
+
+void connect_resolver_host(struct host *host)
+{
+    struct connection *con = host->connections;
+
+    while (con)
+    {
+        if (con->state == Conn_Resolving)
+        {
+            if (!host->ipport) /* unresolved */
+            {
+                connection_destroy(con);
+                /* start all over .. at some point it will be NULL */
+                con = host->connections;
+            }
+            else if (!con->client)
+            {
+                yaz_log(YLOG_WARN, "connect_unresolved_host : ophan client");
+                connection_destroy(con);
+                /* start all over .. at some point it will be NULL */
+                con = host->connections;
+            }
+            else
+            {
+                connection_connect(con);
+                con = con->next;
+            }
+        }
     }
+}
+
+
+// Creates a new connection for client, associated with the host of 
+// client's database
+static struct connection *connection_create(struct client *cl)
+{
+    struct connection *new;
+    struct host *host = cl->database->database->host;
 
     if ((new = connection_freelist))
         connection_freelist = new->next;
@@ -1180,17 +1236,15 @@ static struct connection *connection_create(struct client *cl)
         new->ibuf = 0;
         new->ibufsize = 0;
     }
-    new->state = Conn_Connecting;
-    new->host = cl->database->database->host;
+    new->host = host;
     new->next = new->host->connections;
     new->host->connections = new;
     new->client = cl;
     cl->connection = new;
-    new->link = link;
-
-    new->iochan = iochan_create(cs_fileno(link), handler, 0);
-    iochan_setdata(new->iochan, new);
-    pazpar2_add_channel(new->iochan);
+    new->link = 0;
+    new->state = Conn_Resolving;
+    if (host->ipport)
+        connection_connect(new);
     return new;
 }
 
@@ -1248,6 +1302,103 @@ 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)
 {
@@ -1471,44 +1622,25 @@ char *search(struct session *se, char *query, char *filter)
         return "NOTARGETS";
 
     se->relevance = 0;
+
     for (cl = se->clients; cl; cl = cl->next)
+    {
+        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;
 }
 
-// Apply a session override to a database
-void session_apply_setting(struct session *se, char *dbname, char *setting, char *value)
-{
-    struct session_database *sdb;
-
-    for (sdb = se->databases; sdb; sdb = sdb->next)
-        if (!strcmp(dbname, sdb->database->url))
-        {
-            struct setting *new = nmem_malloc(se->session_nmem, sizeof(*new));
-            int offset = settings_offset(setting);
-
-            if (offset < 0)
-            {
-                yaz_log(YLOG_WARN, "Unknown setting %s", setting);
-                return;
-            }
-            new->precedence = 0;
-            new->target = dbname;
-            new->name = setting;
-            new->value = value;
-            new->next = sdb->settings[offset];
-            sdb->settings[offset] = new;
-            break;
-        }
-    if (!sdb)
-        yaz_log(YLOG_WARN, "Unknown database in setting override: %s", dbname);
-}
-
-void session_init_databases_fun(void *context, struct database *db)
+// Creates a new session_database object for a database
+static void session_init_databases_fun(void *context, struct database *db)
 {
     struct session *se = (struct session *) context;
     struct session_database *new = nmem_malloc(se->session_nmem, sizeof(*new));
@@ -1516,13 +1648,30 @@ 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];
+    memset(new->settings, 0, sizeof(struct settings*) * num);
+    if (db->settings)
+    {
+        for (i = 0; i < num; i++)
+            new->settings[i] = db->settings[i];
+    }
     new->next = se->databases;
     se->databases = new;
 }
 
+// Doesn't free memory associated with sdb -- nmem takes care of that
+static void session_database_destroy(struct session_database *sdb)
+{
+    struct database_retrievalmap *m;
+
+    for (m = sdb->map; m; m = m->next)
+        xsltFreeStylesheet(m->stylesheet);
+    if (sdb->yaz_marc)
+        yaz_marc_destroy(sdb->yaz_marc);
+}
+
 // Initialize session_database list -- this represents this session's view
 // of the database list -- subject to modification by the settings ws command
 void session_init_databases(struct session *se)
@@ -1531,11 +1680,80 @@ void session_init_databases(struct session *se)
     grep_databases(se, 0, session_init_databases_fun);
 }
 
+// Probably session_init_databases_fun should be refactored instead of
+// called here.
+static struct session_database *load_session_database(struct session *se, char *id)
+{
+    struct database *db = find_database(id, 0);
+
+    session_init_databases_fun((void*) se, db);
+    // New sdb is head of se->databases list
+    return se->databases;
+}
+
+// Find an existing session database. If not found, load it
+static struct session_database *find_session_database(struct session *se, char *id)
+{
+    struct session_database *sdb;
+
+    for (sdb = se->databases; sdb; sdb = sdb->next)
+        if (!strcmp(sdb->database->url, id))
+            return sdb;
+    return load_session_database(se, id);
+}
+
+// Apply a session override to a database
+void session_apply_setting(struct session *se, char *dbname, char *setting, char *value)
+{
+    struct session_database *sdb = find_session_database(se, dbname);
+    struct setting *new = nmem_malloc(se->session_nmem, sizeof(*new));
+    int offset = settings_offset(setting);
+
+    if (offset < 0)
+    {
+        yaz_log(YLOG_WARN, "Unknown setting %s", setting);
+        return;
+    }
+    new->precedence = 0;
+    new->target = dbname;
+    new->name = setting;
+    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;
+    }
+}
+
 void destroy_session(struct session *s)
 {
+    struct session_database *sdb;
+
     yaz_log(YLOG_LOG, "Destroying session");
     while (s->clients)
         client_destroy(s->clients);
+    for (sdb = s->databases; sdb; sdb = sdb->next)
+        session_database_destroy(sdb);
     nmem_destroy(s->nmem);
     wrbuf_destroy(s->wrbuf);
 }