SRW, CQL, 2003
[yaz-moved-to-github.git] / srwapps / srw-gateway.c
diff --git a/srwapps/srw-gateway.c b/srwapps/srw-gateway.c
new file mode 100644 (file)
index 0000000..77591db
--- /dev/null
@@ -0,0 +1,903 @@
+/* $Id: srw-gateway.c,v 1.1 2003-01-06 08:20:28 adam Exp $
+   Copyright (C) 2002-2003
+   Index Data Aps
+
+This file is part of the YAZ toolkit.
+
+See the file LICENSE.
+*/
+
+/*
+ * TODO:
+ *
+ * TTL for targets. Separate thread for cleanup.
+ * External target config and aliases.
+ */
+
+/* note that soapH.h defines _REENTRANT so we check for it here */
+#ifdef _REENTRANT
+#include <pthread.h>
+#define USE_THREADS 1
+#else
+#define USE_THREADS 0
+#endif
+
+#include <yaz/srw-util.h>
+#include <yaz/xmalloc.h>
+#include <yaz/zoom.h>
+#include <yaz/log.h>
+#include <yaz/options.h>
+#include <yaz/wrbuf.h>
+
+#define RESULT_SETS 0
+#define SRW_DEBUG 1
+
+struct tset {
+    ZOOM_resultset m_r;
+    long m_expiry_sec;   /* date of creation */
+    int m_idle_time;
+    char *m_query;
+    char *m_schema;
+    struct tset *m_next;
+};
+
+struct target {
+    ZOOM_connection m_c;
+    char *m_name;
+    int m_in_use;
+    struct tset *m_sets;
+    struct target *next;
+};
+
+struct srw_prop {
+    int optimize_level;
+    int idle_time;
+    int max_sets;
+    xslt_maps maps;
+};
+
+static cql_transform_t cql_transform_handle = 0;
+static struct target *target_list = 0;
+#if USE_THREADS
+static pthread_mutex_t target_mutex = PTHREAD_MUTEX_INITIALIZER;
+#define mylock(x) pthread_mutex_lock(x)
+#define myunlock(x) pthread_mutex_unlock(x)
+#else
+#define mylock(x)
+#define myunlock(x)
+#endif
+
+#define ERROR_NO_TARGET -1
+#define ERROR_BAD_CQL   10
+
+static int diag_bib1_to_srw (int code)
+{
+    static int map[] = {
+        1, 1,
+        2, 2,
+        3, 11,
+        4, 35,
+        5, 12,
+        6, 30,
+        7, 30,
+        8, 32,
+        9, 29,
+        10, 10,
+        11, 12,
+        13, 61,
+        14, 63,
+        15, 68,
+        16, 70,
+        17, 70,
+        18, 50,
+        19, 55,
+        20, 56, 
+        21, 52,
+        22, 50,
+        /* 23-26 no map */
+        27, 51,
+        28, 52,
+        29, 52,
+        30, 51,
+        31, 52,
+        32, 52,
+        33, 52,
+        /* 100 -105 */
+        106, 66,
+        107, 11,
+        108, 10,
+        109, 2,
+        110, 37,
+        /* 111- 112 */
+        113, 10,
+        114, 16,
+        115, 16,
+        116, 16,
+        117, 19,
+        118, 22,
+        119, 32,
+        120, 28,
+        121, 15,
+        122, 32,
+        123, 22,
+        124, 24,
+        125, 36,
+        126, 36, 
+        127, 36,
+        128, 51,
+        129, 39,
+        130, 43,
+        131, 40,
+        132, 42,
+        201, 44,
+        202, 41,
+        203, 43,
+        /* 205 */
+        0
+    };
+    const int *p = map;
+    while (*p)
+    {
+        if (code == *p)
+            return p[1];
+        p += 2;
+    }
+    return 0;
+}
+
+static int searchRetrieve(void *userinfo,
+                          struct soap * soap,
+                          xsd__string  *query,
+                          struct xcql__operandType *xQuery,    
+                          xsd__string *sortKeys,
+                          struct xsort__xSortKeysType *xSortKeys,
+                          xsd__integer *startRecord,
+                          xsd__integer *maximumRecords,
+                          xsd__string *recordSchema,
+                          xsd__string *recordPacking,
+                          struct zs__searchRetrieveResponse *res);
+static int explain (void *userinfo,
+                    struct soap *soap,
+                    struct zs__explainResponse *explainResponse);
+
+struct target *target_use (const char *action, const char *query,
+                           const char *schema, struct tset **set,
+                           struct srw_prop *prop)
+{
+    char name[80];
+    struct target *l = 0;
+    struct timeval tv;
+    struct tset **ssp = 0;
+    long now;
+    int no_sets = 0;
+
+    gettimeofday(&tv, 0);
+    now = tv.tv_sec;
+
+    if (strlen(action) >= 80)
+        action = "localhost:210";
+    if (strchr(action, '/') || strchr(action, ':'))
+        strcpy (name, action);
+    else
+    {
+        strcpy (name, "localhost/");
+        if (*action == '\0')
+            strcat (name, "Default");
+        else
+            strcat (name, action);
+    }
+
+    /* See if we have the target and the same query */
+    if (query)
+        for (l = target_list; l; l = l->next)
+            if (!l->m_in_use && !strcmp (l->m_name, name))
+            {
+                struct tset *s = l->m_sets;
+                for (; s; s = s->m_next)
+                    if (!strcmp(s->m_query, query) && 
+                        !strcmp (s->m_schema, schema))
+                    {
+                        *set = s;
+                        return l;
+                    }
+            }
+    
+    /* OK, see if we have the target, then.. */
+    for (l = target_list; l; l = l->next)
+        if (!strcmp (l->m_name, name) && !l->m_in_use)
+        {
+            struct tset *s = l->m_sets;
+            for (; s; s = s->m_next)
+                /* if m_expiry_sec is 0, the condition is true below */
+                if (s->m_expiry_sec < now)
+                {
+                    xfree (s->m_query);
+                    s->m_query = xstrdup("");
+                    xfree (s->m_schema);
+                    s->m_schema = xstrdup("");
+                    ZOOM_resultset_destroy(s->m_r);
+                    s->m_r = 0;
+                    *set = s;
+                    return l;
+                }
+            break;
+        }
+    if (!l)
+    {
+        /* allright. Have to make a new one */
+        l = xmalloc (sizeof(*l));
+        l->m_name = xstrdup (name);
+        l->m_in_use = 1;
+        l->m_c = 0;
+        l->m_sets = 0;
+        l->next = target_list;
+        target_list = l;
+    }
+    for (ssp = &l->m_sets; *ssp; ssp = &(*ssp)->m_next)
+        no_sets++;
+    *ssp = xmalloc(sizeof(**ssp));
+    (*ssp)->m_next = 0;
+    (*ssp)->m_query = xstrdup("");
+    (*ssp)->m_schema = xstrdup("");
+    (*ssp)->m_r = 0;
+    (*ssp)->m_expiry_sec = 0;
+    (*ssp)->m_idle_time = (no_sets >= prop->max_sets ? 0 : prop->idle_time);
+    *set = *ssp;
+    return l;
+}
+
+static void target_destroy (struct target *t)
+{
+    struct target **tp;
+
+    mylock(&target_mutex);
+
+    for (tp = &target_list; *tp; tp = &(*tp)->next)
+        if (*tp == t)
+        {
+            struct tset *s = t->m_sets;
+            while (s)
+            {
+                struct tset *s_next = s->m_next;
+                xfree (s->m_query);
+                xfree (s->m_schema);
+                ZOOM_resultset_destroy (s->m_r);
+                xfree (s);
+                s = s_next;
+            }
+
+            *tp = t->next;
+
+            ZOOM_connection_destroy (t->m_c);
+
+            xfree (t->m_name);
+            xfree (t);
+            break;
+        }
+    myunlock(&target_mutex);
+}
+    
+static void target_leave (struct target *l)
+{
+    mylock(&target_mutex);
+    l->m_in_use = 0;
+
+    if (1)
+    {
+        struct tset *s = l->m_sets;
+        for (; s; s = s->m_next)
+            yaz_log(LOG_LOG, " set %s q=%s", 
+                    (s->m_r ? ZOOM_resultset_option_get(s->m_r,"setname"):""),
+                    s->m_query);
+    }
+    myunlock(&target_mutex);
+}
+
+static void standalone(struct soap *soap, const char *host, int port,
+                       int max_thr, struct srw_prop *properties)
+{
+    struct soap **soap_thr = malloc (sizeof(*soap_thr) * max_thr);
+#if USE_THREADS
+    pthread_t *tid = malloc (sizeof(pthread_t) * max_thr);
+#endif
+    int m, s, i;
+    int cno = 0;
+    int stop = 0;
+    char fname[40];
+    
+    m = soap_bind(soap, 0, port, 100);
+    if (m < 0)
+    {
+        yaz_log (LOG_WARN|LOG_ERRNO, "Cannot bind to %d", port);
+        stop = 1;
+    }
+
+    for (i = 0; i<max_thr; i++)
+        soap_thr[i] = 0;
+
+    while (!stop)
+    {
+        for (i = 0; i<max_thr; i++)
+        {
+            s = soap_accept(soap);
+            if (s < 0)
+                break;
+            cno++;
+            if (!soap_thr[i])
+            {
+                soap_thr[i] = soap_new();
+                if (!soap_thr[i])
+                {
+                    stop = 1;
+                    break;
+                }
+            }
+            else
+            {
+#if USE_THREADS
+                if (max_thr > 1)          /* static mode for max_thr <= 1 */
+                    pthread_join(tid[i], 0);
+#endif
+                soap_end(soap_thr[i]);
+            }
+
+#if SRW_DEBUG
+            sprintf (fname, "srw.recv.%05d.log", cno);
+           remove (fname);
+            soap_set_recv_logfile(soap_thr[i], fname);
+            
+            sprintf (fname, "srw.sent.%05d.log", cno);
+           remove (fname);
+            soap_set_sent_logfile(soap_thr[i], fname);
+            
+            sprintf (fname, "srw.test.%05d.log", cno);
+           remove (fname);
+            soap_set_test_logfile(soap_thr[i], fname);
+
+            yaz_log (LOG_LOG, "starting session %d %ld.%ld.%ld.%ld", cno,
+                     (long) (soap->ip>>24) & 0xff,
+                     (long) (soap->ip>>16) & 0xff,
+                     (long) (soap->ip>>8) & 0xff,
+                     (long) soap->ip & 0xff);
+#endif
+            soap_thr[i]->encodingStyle = 0;
+            soap_thr[i]->socket = s;
+            soap_thr[i]->user = properties;
+#if USE_THREADS
+            if (max_thr <= 1)
+                yaz_srw_serve(soap_thr[i], properties,
+                              searchRetrieve, explain);  /* static mode .. */
+            else
+                pthread_create(&tid[i], 0, (void*(*)(void*))soap_serve,
+                               soap_thr[i]);
+#else
+            yaz_srw_serve(soap_thr[i], properties,
+                          searchRetrieve, explain);  /* static mode .. */
+#endif
+        }
+    }
+#if USE_THREADS
+    free (tid);
+#endif
+    free (soap_thr);
+}
+
+static void reconnect (struct target *t)
+{
+    struct tset *s;
+
+    for (s = t->m_sets; s; s = s->m_next)
+    {
+        ZOOM_resultset_destroy(s->m_r);
+        s->m_r = 0;
+    }
+    ZOOM_connection_destroy (t->m_c);
+
+    t->m_c = ZOOM_connection_create (0);
+    ZOOM_connection_connect (t->m_c, t->m_name, 0);
+}
+
+int explain (void *userinfo,
+             struct soap *soap,
+             struct zs__explainResponse *explainResponse)
+{
+    explainResponse->Explain = 
+        "<explain>\n"
+        "  <!-- not implemented -->\n"
+        "</explain>\n";
+    return SOAP_OK;
+}
+
+int fetchone(struct soap *soap, struct srw_prop *properties,
+             ZOOM_record zrec, const char *schema,
+             char **rec_data, char **rec_schema)
+{
+    xslt_map_result res;
+    int xml_len;
+    const char *xml_rec = ZOOM_record_get(zrec, "xml", &xml_len);
+    if (!xml_rec)
+    {
+        return 65;
+    }
+    if (!strcmp(schema, "MARC21"))
+    {
+        *rec_data = soap_malloc (soap, xml_len+1);
+        memcpy (*rec_data, xml_rec, xml_len);
+        (*rec_data)[xml_len] = 0;
+        *rec_schema = "http://www.loc.gov/marcxml/";
+    }
+    else if ((res = xslt_map (properties->maps, "MARC21",
+                              schema, xml_rec, xml_len)))
+    {
+        int len = xslt_map_result_len(res);
+        char *buf = xslt_map_result_buf(res);
+
+        *rec_data = soap_malloc (soap, len+1);
+        memcpy (*rec_data, buf, len);
+        (*rec_data)[len] = 0;
+        
+        *rec_schema = soap_malloc(soap,
+                                  strlen(xslt_map_result_schema(res)) + 1);
+        strcpy(*rec_schema, xslt_map_result_schema(res));
+        
+        xslt_map_free (res);
+    }
+    else
+    {
+        *rec_data = soap_malloc(soap, strlen(schema)+1);
+        strcpy(*rec_data, schema);
+        return 66;
+    }
+    return 0;
+}
+                
+int searchRetrieve(void *userinfo,
+                   struct soap * soap,
+                   xsd__string  *query,
+                   struct xcql__operandType *xQuery,   
+                   xsd__string *sortKeys,
+                   struct xsort__xSortKeysType *xSortKeys,
+                   xsd__integer *startRecord,
+                   xsd__integer *maximumRecords,
+                   xsd__string *recordSchema,
+                   xsd__string *recordPacking,
+                   struct zs__searchRetrieveResponse *res)
+{
+    const char *msg = 0, *addinfo = 0;
+    const char *schema = recordSchema ? *recordSchema : "";
+    struct target *t = 0;
+    struct tset *s = 0;
+    int error = 0;
+    struct srw_prop *properties = (struct srw_prop*) userinfo;
+    WRBUF wr_log = wrbuf_alloc();
+
+    char pqf_buf[1024];
+    char zurl[81];
+
+    *pqf_buf = '\0';
+    *zurl = '\0';
+    yaz_log (LOG_LOG, "HTTP: %s", soap->endpoint);
+    if (*soap->endpoint)
+    {
+        const char *cp = strstr(soap->endpoint, "//");
+       if (cp)
+            cp = cp+2;             /* skip method// */
+       else
+            cp = soap->endpoint;
+       cp = strstr(cp, "/"); 
+       if (cp)
+        {
+            size_t len;
+            cp++;
+            len = strlen(cp);
+            if (len > 80)
+                len = 80;
+            if (len)
+                memcpy (zurl, cp, len);
+            zurl[len] = '\0';
+        }
+    }
+    else
+    {
+        const char *cp = getenv("PATH_INFO");
+        if (cp && cp[0] && cp[1])
+        {
+            size_t len;
+            cp++;  /* skip / */
+            len = strlen(cp);
+            if (len > 80)
+                len = 80;
+            if (len)
+                memcpy (zurl, cp, len);
+            zurl[len] = '\0';
+        }
+    }
+    if (query)
+    {
+        CQL_parser cql_parser = cql_parser_create();
+        int r = cql_parser_string(cql_parser, *query);
+
+        if (r)
+        {
+            yaz_log (LOG_LOG, "cql failed: %s", *query);
+            error = ERROR_BAD_CQL;
+        }
+        else
+        {
+            struct cql_node *tree = cql_parser_result(cql_parser);
+            error = cql_transform_buf (cql_transform_handle, tree,
+                                       pqf_buf, sizeof(pqf_buf));
+            if (error)
+                cql_transform_error(cql_transform_handle, &addinfo);
+            cql_parser_destroy(cql_parser);
+            yaz_log (LOG_LOG, "cql OK: %s", *query);
+        }
+    }
+    else if (xQuery)
+    {
+        struct cql_node *tree = xcql_to_cqlnode(xQuery);
+        yaz_log (LOG_LOG, "xcql");
+        cql_transform_buf (cql_transform_handle, tree,
+                           pqf_buf, sizeof(pqf_buf));
+        cql_node_destroy(tree);
+    }
+    if (!error)
+    {
+        mylock(&target_mutex);
+        t = target_use (zurl, *pqf_buf ? pqf_buf : 0, schema, &s, properties);
+        myunlock(&target_mutex);
+    }
+
+    if (!error && !t->m_c)
+    {
+        reconnect(t);
+        if (ZOOM_connection_error (t->m_c, &msg, &addinfo))
+        {
+            yaz_log (LOG_LOG, "%s: connect failed", t->m_name);
+            error = ERROR_NO_TARGET;
+        }
+        else
+            yaz_log (LOG_LOG, "%s: connect ok", t->m_name);
+    }
+    if (!error && t->m_c &&  *pqf_buf)
+    {
+        if (properties->optimize_level <=1 ||
+            strcmp (pqf_buf, s->m_query) ||
+            strcmp (schema, s->m_schema))
+        {
+            /* not the same query: remove result set .. */
+            ZOOM_resultset_destroy (s->m_r);
+            s->m_r = 0;
+        }
+        else
+        {
+            /* same query: retrieve (instead of doing search) */
+            if (maximumRecords && *maximumRecords > 0)
+            {
+                int start = startRecord ? *startRecord : 1;
+                yaz_log (LOG_LOG, "%s: present start=%d count=%d pqf=%s",
+                         t->m_name, start, *maximumRecords, pqf_buf);
+                wrbuf_printf (wr_log, "%s: present start=%d count=%d pqf=%s",
+                              t->m_name, start, *maximumRecords, pqf_buf);
+                ZOOM_resultset_records (s->m_r, 0, start-1, *maximumRecords);
+                error = ZOOM_connection_error (t->m_c, &msg, &addinfo);
+                if (error == ZOOM_ERROR_CONNECTION_LOST ||
+                    error == ZOOM_ERROR_CONNECT)
+                {
+                    reconnect (t);
+                    if ((error = ZOOM_connection_error (t->m_c, &msg,
+                                                    &addinfo)))
+                    {
+                        yaz_log (LOG_LOG, "%s: connect failed", t->m_name);
+                        error = ERROR_NO_TARGET;
+                    }
+                }
+                else if (error)
+                {
+                    yaz_log (LOG_LOG, "%s: present failed bib1-code=%d",
+                             t->m_name, error);
+                    error = diag_bib1_to_srw(error);
+                }
+            }
+            else
+            {
+                yaz_log (LOG_LOG, "%s: matched search pqf=%s",
+                         t->m_name, pqf_buf);
+                wrbuf_printf (wr_log, "%s: matched search pqf=%s",
+                         t->m_name, pqf_buf);
+            }
+        }
+        if (!error && !s->m_r) 
+        {   /* no result set usable. We must search ... */
+            int pass;
+            for (pass = 0; pass < 2; pass++)
+            {
+                char val[30];
+                int start = startRecord ? *startRecord : 1;
+                int count = maximumRecords ? *maximumRecords : 0;
+                
+                sprintf (val, "%d", start-1);
+                ZOOM_connection_option_set (t->m_c, "start", val);
+                
+                sprintf (val, "%d", count);
+                ZOOM_connection_option_set (t->m_c, "count", val);
+                
+                ZOOM_connection_option_set (t->m_c, "preferredRecordSyntax", 
+                                            "usmarc");
+                
+                xfree (s->m_query);
+                s->m_query = xstrdup (pqf_buf);
+
+                xfree (s->m_schema);
+                s->m_schema = xstrdup (schema);
+                
+                yaz_log (LOG_LOG, "%s: search start=%d count=%d pqf=%s",
+                         t->m_name, start, count, pqf_buf);
+                
+                wrbuf_printf (wr_log, "%s: search start=%d count=%d pqf=%s",
+                              t->m_name, start, count, pqf_buf);
+                
+                s->m_r = ZOOM_connection_search_pqf (t->m_c, s->m_query);
+                
+                error = ZOOM_connection_error (t->m_c, &msg, &addinfo);
+                if (!error)
+                    break;
+                if (error != ZOOM_ERROR_CONNECTION_LOST &&
+                    error != ZOOM_ERROR_CONNECT)
+                {
+                    yaz_log (LOG_LOG, "%s: search failed bib1-code=%d",
+                             t->m_name, error);
+                    error = diag_bib1_to_srw(error);
+                    break;
+                }
+                yaz_log (LOG_LOG, "%s: reconnect (search again)", t->m_name);
+
+                /* try once more */
+                reconnect(t);
+
+                error = ZOOM_connection_error (t->m_c, &msg, &addinfo);
+
+                if (error)
+                {
+                    error = ERROR_NO_TARGET;
+                    break;
+                }
+            }
+        }
+    }
+    
+    if (!error && t->m_c && s->m_r)
+    {
+        yaz_log (LOG_LOG, "%s: %d hits", t->m_name,
+                 ZOOM_resultset_size(s->m_r));
+        res->numberOfRecords = ZOOM_resultset_size(s->m_r);
+        
+        if (maximumRecords)
+        {
+            int i, j = 0;
+            int offset = startRecord ? *startRecord -1 : 0;
+            res->records.record =
+                soap_malloc(soap, sizeof(*res->records.record) *
+                            *maximumRecords);
+            
+            for (i = 0; i < *maximumRecords; i++)
+            {
+                char *rec_data = 0;
+                char *rec_schema = 0;
+                ZOOM_record zrec = ZOOM_resultset_record (s->m_r, offset + i);
+                if (!zrec)
+                {
+                    error = 65;
+                    addinfo = schema;
+                    break;
+                }
+                error = fetchone(soap, properties, zrec, schema,
+                                 &rec_data, &rec_schema);
+                if (error)
+                {
+                    addinfo = rec_data;
+                    break;
+                }
+                res->records.record[j] = 
+                    soap_malloc(soap, sizeof(**res->records.record));
+                res->records.record[j]->recordData = rec_data;
+                res->records.record[j]->recordSchema = rec_schema;
+                j++;
+            }
+            res->records.__sizeRecords = j;
+        }
+        else
+            res->numberOfRecords = 0;
+    }
+    if (error)
+    {
+        if (s)
+        {
+            ZOOM_resultset_destroy (s->m_r);
+            s->m_r = 0;
+        }
+        if (error == ERROR_NO_TARGET)
+        {
+            addinfo = zurl;
+            ZOOM_connection_destroy (t->m_c);
+            t->m_c = 0;
+        }
+        else
+        {
+            res->diagnostics.__sizeDiagnostics = 1;
+            res->diagnostics.diagnostic =
+                soap_malloc (soap, sizeof(*res->diagnostics.diagnostic));
+            res->diagnostics.diagnostic[0] =
+                soap_malloc (soap, sizeof(**res->diagnostics.diagnostic));
+            
+            res->diagnostics.diagnostic[0]->code = error;
+            if (addinfo)
+            {
+                res->diagnostics.diagnostic[0]->details =
+                    soap_malloc (soap, strlen(addinfo) + 1);
+                strcpy (res->diagnostics.diagnostic[0]->details, addinfo);
+            }
+            else
+                res->diagnostics.diagnostic[0]->details = 0;
+        }
+    }
+    else
+    {
+        if (s->m_r)
+        {
+            struct timeval tv;
+            const char *setname = ZOOM_resultset_option_get(s->m_r, "setname");
+            if (strcmp(setname, "default") && s->m_idle_time)
+            {
+                res->resultSetId = soap_malloc(soap, strlen(setname));
+                strcpy(res->resultSetId, setname);
+                res->resultSetIdleTime = s->m_idle_time;
+                gettimeofday(&tv, 0);
+                s->m_expiry_sec = res->resultSetIdleTime + tv.tv_sec + 2;
+            } else {
+                s->m_expiry_sec = 0;
+            }
+        }
+    }
+
+    if (t)
+    {
+        if (properties->optimize_level > 0)
+            target_leave(t);
+        else
+            target_destroy(t);
+    }
+    wrbuf_free(wr_log, 1);
+    if (error == ERROR_NO_TARGET)
+        return soap_receiver_fault(soap, "Cannot connect to Z39.50 target", 0);
+    return SOAP_OK;
+}
+
+int main(int argc, char **argv)
+{
+    struct soap soap;
+    int ret;
+    int port = 0;
+    int no_threads = 40;
+    char *arg;
+    const char *host = 0;
+    struct srw_prop properties;
+
+    properties.optimize_level = 2;
+    properties.idle_time = 300;
+    properties.max_sets = 30;
+    properties.maps = 0;
+            
+    while ((ret = options("dO:T:l:hVp:s:x:i:", argv, argc, &arg)) != -2)
+    {
+        switch(ret)
+        {
+        case 0:
+            port = atoi(arg);
+            break;
+        case 'O':
+            properties.optimize_level = atoi(arg);
+            break;
+        case 'T':
+            no_threads = atoi(arg);
+            if (no_threads < 1 || no_threads > 200)
+                no_threads = 40;
+            break;
+        case 's':
+            if (!properties.maps)
+            {
+                properties.maps = xslt_maps_create();
+                if (xslt_maps_file(properties.maps, arg))
+                {
+                    fprintf (stderr, "maps file %s could not be opened\n",
+                             arg);
+                    exit(1);
+                }
+            }
+            break;
+        case 'l':
+            yaz_log_init_file(arg);
+            break;
+        case 'V':
+            puts ("Version: $Id: srw-gateway.c,v 1.1 2003-01-06 08:20:28 adam Exp $"
+#if SRW_DEBUG
+            " DEBUG"
+#endif
+                );
+            exit (0);
+        case 'p':
+            if (cql_transform_handle == 0)
+                cql_transform_handle = cql_transform_open_fname(arg);
+            break;
+        case 'x':
+            properties.max_sets = atoi(arg);
+            break;
+        case 'i':
+            properties.idle_time = atoi(arg);
+            break;
+        case 'h':
+            printf ("srw-gateway [options] <port>\n");
+            printf ("  port  port for standalone service; If port is omitted, CGI is used.\n");
+            printf ("  -O n     optimize level. >= 1 cache connections, >=2 cache result sets.\n");
+#if USE_THREADS
+            printf ("  -T n     number of threads.\n");
+#else
+            printf ("  -T       unsupported in this version.\n");
+#endif
+            printf ("  -l file  log to file (instead of stderr).\n");
+            printf ("  -p file  PQF properties.\n");
+            printf ("  -s file  schema maps.\n");
+            printf ("  -i time  idle time.\n");
+            printf ("  -x sets  live sets.\n");
+            printf ("  -V       show version.\n");
+            exit (1);
+        default:
+            fprintf (stderr, "srw-gateway: bad option -%s ; use -h for help\n",
+                     arg);
+            exit (1);
+            break;
+            
+        }
+    }
+    if (!cql_transform_handle)
+        cql_transform_handle = cql_transform_open_fname("pqf.properties");
+    if (!properties.maps)
+    {
+        properties.maps = xslt_maps_create();
+        xslt_maps_file(properties.maps, "maps.xml");
+    }
+    soap.encodingStyle = 0;
+    if (port == 0 && getenv("QUERY_STRING"))
+    {
+        properties.optimize_level = 0;
+
+        yaz_log_init_file("srw.log");
+        yaz_log (LOG_LOG, "CGI begin");
+        soap_init(&soap);
+        soap.user = &properties;
+
+        yaz_srw_serve(&soap, &properties, searchRetrieve, explain);
+
+        soap_end(&soap);
+        yaz_log (LOG_LOG, "CGI end");
+    }
+    else if (port)
+    {
+        if (!cql_transform_handle)
+        {
+            fprintf(stderr, "no properties file; use option -p to specify\n");
+            exit (1);
+        }
+        yaz_log (LOG_LOG, "standalone service on port %d", port);
+        
+        soap_init(&soap);
+
+        standalone(&soap, host, port, no_threads, &properties);
+    }
+    else
+    {
+        fprintf(stderr, "srw-gateway: no port specified. Use -h for help\n");
+    }
+    xslt_maps_free(properties.maps);
+    return 0;
+}