Changed prototype of yaz_srw_serve to solve threading problem
[yaz-moved-to-github.git] / srwapps / srw-gateway.c
1 /* $Id: srw-gateway.c,v 1.2 2003-01-20 13:04:50 adam Exp $
2    Copyright (C) 2002-2003
3    Index Data Aps
4
5 This file is part of the YAZ toolkit.
6
7 See the file LICENSE.
8 */
9
10 /*
11  * TODO:
12  *
13  * TTL for targets. Separate thread for cleanup.
14  * External target config and aliases.
15  */
16
17 /* note that soapH.h defines _REENTRANT so we check for it here */
18 #ifdef _REENTRANT
19 #include <pthread.h>
20 #define USE_THREADS 1
21 #else
22 #define USE_THREADS 0
23 #endif
24
25 #include <yaz/srw-util.h>
26 #include <yaz/xmalloc.h>
27 #include <yaz/zoom.h>
28 #include <yaz/log.h>
29 #include <yaz/options.h>
30 #include <yaz/wrbuf.h>
31
32 #define RESULT_SETS 0
33 #define SRW_DEBUG 1
34
35 struct tset {
36     ZOOM_resultset m_r;
37     long m_expiry_sec;   /* date of creation */
38     int m_idle_time;
39     char *m_query;
40     char *m_schema;
41     struct tset *m_next;
42 };
43
44 struct target {
45     ZOOM_connection m_c;
46     char *m_name;
47     int m_in_use;
48     struct tset *m_sets;
49     struct target *next;
50 };
51
52 struct srw_prop {
53     int optimize_level;
54     int idle_time;
55     int max_sets;
56     xslt_maps maps;
57 };
58
59 static cql_transform_t cql_transform_handle = 0;
60 static struct target *target_list = 0;
61 #if USE_THREADS
62 static pthread_mutex_t target_mutex = PTHREAD_MUTEX_INITIALIZER;
63 #define mylock(x) pthread_mutex_lock(x)
64 #define myunlock(x) pthread_mutex_unlock(x)
65 #else
66 #define mylock(x)
67 #define myunlock(x)
68 #endif
69
70 #define ERROR_NO_TARGET -1
71 #define ERROR_BAD_CQL   10
72
73 static int diag_bib1_to_srw (int code)
74 {
75     static int map[] = {
76         1, 1,
77         2, 2,
78         3, 11,
79         4, 35,
80         5, 12,
81         6, 30,
82         7, 30,
83         8, 32,
84         9, 29,
85         10, 10,
86         11, 12,
87         13, 61,
88         14, 63,
89         15, 68,
90         16, 70,
91         17, 70,
92         18, 50,
93         19, 55,
94         20, 56, 
95         21, 52,
96         22, 50,
97         /* 23-26 no map */
98         27, 51,
99         28, 52,
100         29, 52,
101         30, 51,
102         31, 52,
103         32, 52,
104         33, 52,
105         /* 100 -105 */
106         106, 66,
107         107, 11,
108         108, 10,
109         109, 2,
110         110, 37,
111         /* 111- 112 */
112         113, 10,
113         114, 16,
114         115, 16,
115         116, 16,
116         117, 19,
117         118, 22,
118         119, 32,
119         120, 28,
120         121, 15,
121         122, 32,
122         123, 22,
123         124, 24,
124         125, 36,
125         126, 36, 
126         127, 36,
127         128, 51,
128         129, 39,
129         130, 43,
130         131, 40,
131         132, 42,
132         201, 44,
133         202, 41,
134         203, 43,
135         /* 205 */
136         0
137     };
138     const int *p = map;
139     while (*p)
140     {
141         if (code == *p)
142             return p[1];
143         p += 2;
144     }
145     return 0;
146 }
147
148 static int searchRetrieve(void *userinfo,
149                           struct soap * soap,
150                           xsd__string  *query,
151                           struct xcql__operandType *xQuery,     
152                           xsd__string *sortKeys,
153                           struct xsort__xSortKeysType *xSortKeys,
154                           xsd__integer *startRecord,
155                           xsd__integer *maximumRecords,
156                           xsd__string *recordSchema,
157                           xsd__string *recordPacking,
158                           struct zs__searchRetrieveResponse *res);
159 static int explain (void *userinfo,
160                     struct soap *soap,
161                     struct zs__explainResponse *explainResponse);
162
163 struct target *target_use (const char *action, const char *query,
164                            const char *schema, struct tset **set,
165                            struct srw_prop *prop)
166 {
167     char name[80];
168     struct target *l = 0;
169     struct timeval tv;
170     struct tset **ssp = 0;
171     long now;
172     int no_sets = 0;
173
174     gettimeofday(&tv, 0);
175     now = tv.tv_sec;
176
177     if (strlen(action) >= 80)
178         action = "localhost:210";
179     if (strchr(action, '/') || strchr(action, ':'))
180         strcpy (name, action);
181     else
182     {
183         strcpy (name, "localhost/");
184         if (*action == '\0')
185             strcat (name, "Default");
186         else
187             strcat (name, action);
188     }
189
190     /* See if we have the target and the same query */
191     if (query)
192         for (l = target_list; l; l = l->next)
193             if (!l->m_in_use && !strcmp (l->m_name, name))
194             {
195                 struct tset *s = l->m_sets;
196                 for (; s; s = s->m_next)
197                     if (!strcmp(s->m_query, query) && 
198                         !strcmp (s->m_schema, schema))
199                     {
200                         *set = s;
201                         return l;
202                     }
203             }
204     
205     /* OK, see if we have the target, then.. */
206     for (l = target_list; l; l = l->next)
207         if (!strcmp (l->m_name, name) && !l->m_in_use)
208         {
209             struct tset *s = l->m_sets;
210             for (; s; s = s->m_next)
211                 /* if m_expiry_sec is 0, the condition is true below */
212                 if (s->m_expiry_sec < now)
213                 {
214                     xfree (s->m_query);
215                     s->m_query = xstrdup("");
216                     xfree (s->m_schema);
217                     s->m_schema = xstrdup("");
218                     ZOOM_resultset_destroy(s->m_r);
219                     s->m_r = 0;
220                     *set = s;
221                     return l;
222                 }
223             break;
224         }
225     if (!l)
226     {
227         /* allright. Have to make a new one */
228         l = xmalloc (sizeof(*l));
229         l->m_name = xstrdup (name);
230         l->m_in_use = 1;
231         l->m_c = 0;
232         l->m_sets = 0;
233         l->next = target_list;
234         target_list = l;
235     }
236     for (ssp = &l->m_sets; *ssp; ssp = &(*ssp)->m_next)
237         no_sets++;
238     *ssp = xmalloc(sizeof(**ssp));
239     (*ssp)->m_next = 0;
240     (*ssp)->m_query = xstrdup("");
241     (*ssp)->m_schema = xstrdup("");
242     (*ssp)->m_r = 0;
243     (*ssp)->m_expiry_sec = 0;
244     (*ssp)->m_idle_time = (no_sets >= prop->max_sets ? 0 : prop->idle_time);
245     *set = *ssp;
246     return l;
247 }
248
249 static void target_destroy (struct target *t)
250 {
251     struct target **tp;
252
253     mylock(&target_mutex);
254
255     for (tp = &target_list; *tp; tp = &(*tp)->next)
256         if (*tp == t)
257         {
258             struct tset *s = t->m_sets;
259             while (s)
260             {
261                 struct tset *s_next = s->m_next;
262                 xfree (s->m_query);
263                 xfree (s->m_schema);
264                 ZOOM_resultset_destroy (s->m_r);
265                 xfree (s);
266                 s = s_next;
267             }
268
269             *tp = t->next;
270
271             ZOOM_connection_destroy (t->m_c);
272
273             xfree (t->m_name);
274             xfree (t);
275             break;
276         }
277     myunlock(&target_mutex);
278 }
279     
280 static void target_leave (struct target *l)
281 {
282     mylock(&target_mutex);
283     l->m_in_use = 0;
284
285     if (1)
286     {
287         struct tset *s = l->m_sets;
288         for (; s; s = s->m_next)
289             yaz_log(LOG_LOG, " set %s q=%s", 
290                     (s->m_r ? ZOOM_resultset_option_get(s->m_r,"setname"):""),
291                     s->m_query);
292     }
293     myunlock(&target_mutex);
294 }
295
296 #if USE_THREADS
297 static void *p_serve (void *p)
298 {
299     struct soap *soap = p;
300     yaz_srw_serve(soap, searchRetrieve, explain);
301 }
302 #endif
303
304
305 static void standalone(struct soap *soap, const char *host, int port,
306                        int max_thr, struct srw_prop *properties)
307 {
308     struct soap **soap_thr = malloc (sizeof(*soap_thr) * max_thr);
309 #if USE_THREADS
310     pthread_t *tid = malloc (sizeof(pthread_t) * max_thr);
311 #endif
312     int m, s, i;
313     int cno = 0;
314     int stop = 0;
315     char fname[40];
316     
317     m = soap_bind(soap, 0, port, 100);
318     if (m < 0)
319     {
320         yaz_log (LOG_WARN|LOG_ERRNO, "Cannot bind to %d", port);
321         stop = 1;
322     }
323
324     for (i = 0; i<max_thr; i++)
325         soap_thr[i] = 0;
326
327     while (!stop)
328     {
329         for (i = 0; i<max_thr; i++)
330         {
331             s = soap_accept(soap);
332             if (s < 0)
333                 break;
334             cno++;
335             if (!soap_thr[i])
336             {
337                 soap_thr[i] = soap_new();
338                 if (!soap_thr[i])
339                 {
340                     stop = 1;
341                     break;
342                 }
343             }
344             else
345             {
346 #if USE_THREADS
347                 if (max_thr > 1)          /* static mode for max_thr <= 1 */
348                     pthread_join(tid[i], 0);
349 #endif
350                 soap_end(soap_thr[i]);
351             }
352
353 #if SRW_DEBUG
354             sprintf (fname, "srw.recv.%05d.log", cno);
355             remove (fname);
356             soap_set_recv_logfile(soap_thr[i], fname);
357             
358             sprintf (fname, "srw.sent.%05d.log", cno);
359             remove (fname);
360             soap_set_sent_logfile(soap_thr[i], fname);
361             
362             sprintf (fname, "srw.test.%05d.log", cno);
363             remove (fname);
364             soap_set_test_logfile(soap_thr[i], fname);
365
366             yaz_log (LOG_LOG, "starting session %d %ld.%ld.%ld.%ld", cno,
367                      (long) (soap->ip>>24) & 0xff,
368                      (long) (soap->ip>>16) & 0xff,
369                      (long) (soap->ip>>8) & 0xff,
370                      (long) soap->ip & 0xff);
371 #endif
372             soap_thr[i]->encodingStyle = 0;
373             soap_thr[i]->socket = s;
374             soap_thr[i]->user = properties;
375 #if USE_THREADS
376             if (max_thr <= 1)
377                 yaz_srw_serve(soap_thr[i],
378                               searchRetrieve, explain);  /* static mode .. */
379             else
380                 pthread_create(&tid[i], 0, p_serve, soap_thr[i]);
381 #else
382             yaz_srw_serve(soap_thr[i],
383                           searchRetrieve, explain);  /* static mode .. */
384 #endif
385         }
386     }
387 #if USE_THREADS
388     free (tid);
389 #endif
390     free (soap_thr);
391 }
392
393 static void reconnect (struct target *t)
394 {
395     struct tset *s;
396
397     for (s = t->m_sets; s; s = s->m_next)
398     {
399         ZOOM_resultset_destroy(s->m_r);
400         s->m_r = 0;
401     }
402     ZOOM_connection_destroy (t->m_c);
403
404     t->m_c = ZOOM_connection_create (0);
405     ZOOM_connection_connect (t->m_c, t->m_name, 0);
406 }
407
408 int explain (void *userinfo,
409              struct soap *soap,
410              struct zs__explainResponse *explainResponse)
411 {
412     explainResponse->Explain = 
413         "<explain>\n"
414         "  <!-- not implemented -->\n"
415         "</explain>\n";
416     return SOAP_OK;
417 }
418
419 int fetchone(struct soap *soap, struct srw_prop *properties,
420              ZOOM_record zrec, const char *schema,
421              char **rec_data, char **rec_schema)
422 {
423     xslt_map_result res;
424     int xml_len;
425     const char *xml_rec = ZOOM_record_get(zrec, "xml", &xml_len);
426     if (!xml_rec)
427     {
428         return 65;
429     }
430     if (!strcmp(schema, "MARC21") || !strcmp(schema, "http://www.loc.gov/marcxml/"))
431     {
432         *rec_data = soap_malloc (soap, xml_len+1);
433         memcpy (*rec_data, xml_rec, xml_len);
434         (*rec_data)[xml_len] = 0;
435         *rec_schema = "http://www.loc.gov/marcxml/";
436     }
437     else if ((res = xslt_map (properties->maps, "MARC21",
438                               schema, xml_rec, xml_len)))
439     {
440         int len = xslt_map_result_len(res);
441         char *buf = xslt_map_result_buf(res);
442
443         *rec_data = soap_malloc (soap, len+1);
444         memcpy (*rec_data, buf, len);
445         (*rec_data)[len] = 0;
446         
447         *rec_schema = soap_malloc(soap,
448                                   strlen(xslt_map_result_schema(res)) + 1);
449         strcpy(*rec_schema, xslt_map_result_schema(res));
450         
451         xslt_map_free (res);
452     }
453     else
454     {
455         *rec_data = soap_malloc(soap, strlen(schema)+1);
456         strcpy(*rec_data, schema);
457         return 66;
458     }
459     return 0;
460 }
461                 
462 int searchRetrieve(void *userinfo,
463                    struct soap * soap,
464                    xsd__string  *query,
465                    struct xcql__operandType *xQuery,    
466                    xsd__string *sortKeys,
467                    struct xsort__xSortKeysType *xSortKeys,
468                    xsd__integer *startRecord,
469                    xsd__integer *maximumRecords,
470                    xsd__string *recordSchema,
471                    xsd__string *recordPacking,
472                    struct zs__searchRetrieveResponse *res)
473 {
474     const char *msg = 0, *addinfo = 0;
475     const char *schema = recordSchema ? *recordSchema : "";
476     struct target *t = 0;
477     struct tset *s = 0;
478     int error = 0;
479     struct srw_prop *properties = (struct srw_prop*) userinfo;
480     WRBUF wr_log = wrbuf_alloc();
481
482     char pqf_buf[1024];
483     char zurl[81];
484
485     *pqf_buf = '\0';
486     *zurl = '\0';
487     yaz_log (LOG_LOG, "HTTP: %s", soap->endpoint);
488     if (*soap->endpoint)
489     {
490         const char *cp = strstr(soap->endpoint, "//");
491         if (cp)
492             cp = cp+2;             /* skip method// */
493         else
494             cp = soap->endpoint;
495         cp = strstr(cp, "/"); 
496         if (cp)
497         {
498             size_t len;
499             cp++;
500             len = strlen(cp);
501             if (len > 80)
502                 len = 80;
503             if (len)
504                 memcpy (zurl, cp, len);
505             zurl[len] = '\0';
506         }
507     }
508     else
509     {
510         const char *cp = getenv("PATH_INFO");
511         if (cp && cp[0] && cp[1])
512         {
513             size_t len;
514             cp++;  /* skip / */
515             len = strlen(cp);
516             if (len > 80)
517                 len = 80;
518             if (len)
519                 memcpy (zurl, cp, len);
520             zurl[len] = '\0';
521         }
522     }
523     if (query)
524     {
525         CQL_parser cql_parser = cql_parser_create();
526         int r = cql_parser_string(cql_parser, *query);
527
528         if (r)
529         {
530             yaz_log (LOG_LOG, "cql failed: %s", *query);
531             error = ERROR_BAD_CQL;
532         }
533         else
534         {
535             struct cql_node *tree = cql_parser_result(cql_parser);
536             error = cql_transform_buf (cql_transform_handle, tree,
537                                        pqf_buf, sizeof(pqf_buf));
538             if (error)
539                 cql_transform_error(cql_transform_handle, &addinfo);
540             cql_parser_destroy(cql_parser);
541             yaz_log (LOG_LOG, "cql OK: %s", *query);
542         }
543     }
544     else if (xQuery)
545     {
546         struct cql_node *tree = xcql_to_cqlnode(xQuery);
547         yaz_log (LOG_LOG, "xcql");
548         cql_transform_buf (cql_transform_handle, tree,
549                            pqf_buf, sizeof(pqf_buf));
550         cql_node_destroy(tree);
551     }
552     if (!error)
553     {
554         mylock(&target_mutex);
555         t = target_use (zurl, *pqf_buf ? pqf_buf : 0, schema, &s, properties);
556         myunlock(&target_mutex);
557     }
558
559     if (!error && !t->m_c)
560     {
561         reconnect(t);
562         if (ZOOM_connection_error (t->m_c, &msg, &addinfo))
563         {
564             yaz_log (LOG_LOG, "%s: connect failed", t->m_name);
565             error = ERROR_NO_TARGET;
566         }
567         else
568             yaz_log (LOG_LOG, "%s: connect ok", t->m_name);
569     }
570     if (!error && t->m_c &&  *pqf_buf)
571     {
572         if (properties->optimize_level <=1 ||
573             strcmp (pqf_buf, s->m_query) ||
574             strcmp (schema, s->m_schema))
575         {
576             /* not the same query: remove result set .. */
577             ZOOM_resultset_destroy (s->m_r);
578             s->m_r = 0;
579         }
580         else
581         {
582             /* same query: retrieve (instead of doing search) */
583             if (maximumRecords && *maximumRecords > 0)
584             {
585                 int start = startRecord ? *startRecord : 1;
586                 yaz_log (LOG_LOG, "%s: present start=%d count=%d pqf=%s",
587                          t->m_name, start, *maximumRecords, pqf_buf);
588                 wrbuf_printf (wr_log, "%s: present start=%d count=%d pqf=%s",
589                               t->m_name, start, *maximumRecords, pqf_buf);
590                 ZOOM_resultset_records (s->m_r, 0, start-1, *maximumRecords);
591                 error = ZOOM_connection_error (t->m_c, &msg, &addinfo);
592                 if (error == ZOOM_ERROR_CONNECTION_LOST ||
593                     error == ZOOM_ERROR_CONNECT)
594                 {
595                     reconnect (t);
596                     if ((error = ZOOM_connection_error (t->m_c, &msg,
597                                                      &addinfo)))
598                     {
599                         yaz_log (LOG_LOG, "%s: connect failed", t->m_name);
600                         error = ERROR_NO_TARGET;
601                     }
602                 }
603                 else if (error)
604                 {
605                     yaz_log (LOG_LOG, "%s: present failed bib1-code=%d",
606                              t->m_name, error);
607                     error = diag_bib1_to_srw(error);
608                 }
609             }
610             else
611             {
612                 yaz_log (LOG_LOG, "%s: matched search pqf=%s",
613                          t->m_name, pqf_buf);
614                 wrbuf_printf (wr_log, "%s: matched search pqf=%s",
615                          t->m_name, pqf_buf);
616             }
617         }
618         if (!error && !s->m_r) 
619         {   /* no result set usable. We must search ... */
620             int pass;
621             for (pass = 0; pass < 2; pass++)
622             {
623                 char val[30];
624                 int start = startRecord ? *startRecord : 1;
625                 int count = maximumRecords ? *maximumRecords : 0;
626                 
627                 sprintf (val, "%d", start-1);
628                 ZOOM_connection_option_set (t->m_c, "start", val);
629                 
630                 sprintf (val, "%d", count);
631                 ZOOM_connection_option_set (t->m_c, "count", val);
632                 
633                 ZOOM_connection_option_set (t->m_c, "preferredRecordSyntax", 
634                                             "usmarc");
635                 
636                 xfree (s->m_query);
637                 s->m_query = xstrdup (pqf_buf);
638
639                 xfree (s->m_schema);
640                 s->m_schema = xstrdup (schema);
641                 
642                 yaz_log (LOG_LOG, "%s: search start=%d count=%d pqf=%s",
643                          t->m_name, start, count, pqf_buf);
644                 
645                 wrbuf_printf (wr_log, "%s: search start=%d count=%d pqf=%s",
646                               t->m_name, start, count, pqf_buf);
647                 
648                 s->m_r = ZOOM_connection_search_pqf (t->m_c, s->m_query);
649                 
650                 error = ZOOM_connection_error (t->m_c, &msg, &addinfo);
651                 if (!error)
652                     break;
653                 if (error != ZOOM_ERROR_CONNECTION_LOST &&
654                     error != ZOOM_ERROR_CONNECT)
655                 {
656                     yaz_log (LOG_LOG, "%s: search failed bib1-code=%d",
657                              t->m_name, error);
658                     error = diag_bib1_to_srw(error);
659                     break;
660                 }
661                 yaz_log (LOG_LOG, "%s: reconnect (search again)", t->m_name);
662
663                 /* try once more */
664                 reconnect(t);
665
666                 error = ZOOM_connection_error (t->m_c, &msg, &addinfo);
667
668                 if (error)
669                 {
670                     error = ERROR_NO_TARGET;
671                     break;
672                 }
673             }
674         }
675     }
676     
677     if (!error && t->m_c && s->m_r)
678     {
679         yaz_log (LOG_LOG, "%s: %d hits", t->m_name,
680                  ZOOM_resultset_size(s->m_r));
681         res->numberOfRecords = ZOOM_resultset_size(s->m_r);
682         
683         if (maximumRecords)
684         {
685             int i, j = 0;
686             int offset = startRecord ? *startRecord -1 : 0;
687             res->records.record =
688                 soap_malloc(soap, sizeof(*res->records.record) *
689                             *maximumRecords);
690             
691             for (i = 0; i < *maximumRecords; i++)
692             {
693                 char *rec_data = 0;
694                 char *rec_schema = 0;
695                 ZOOM_record zrec = ZOOM_resultset_record (s->m_r, offset + i);
696                 if (!zrec)
697                 {
698                     error = 65;
699                     addinfo = schema;
700                     break;
701                 }
702                 error = fetchone(soap, properties, zrec, schema,
703                                  &rec_data, &rec_schema);
704                 if (error)
705                 {
706                     addinfo = rec_data;
707                     break;
708                 }
709                 res->records.record[j] = 
710                     soap_malloc(soap, sizeof(**res->records.record));
711                 res->records.record[j]->recordData = rec_data;
712                 res->records.record[j]->recordSchema = rec_schema;
713                 j++;
714             }
715             res->records.__sizeRecords = j;
716         }
717         else
718             res->numberOfRecords = 0;
719     }
720     if (error)
721     {
722         if (s)
723         {
724             ZOOM_resultset_destroy (s->m_r);
725             s->m_r = 0;
726         }
727         if (error == ERROR_NO_TARGET)
728         {
729             addinfo = zurl;
730             ZOOM_connection_destroy (t->m_c);
731             t->m_c = 0;
732         }
733         else
734         {
735             res->diagnostics.__sizeDiagnostics = 1;
736             res->diagnostics.diagnostic =
737                 soap_malloc (soap, sizeof(*res->diagnostics.diagnostic));
738             res->diagnostics.diagnostic[0] =
739                 soap_malloc (soap, sizeof(**res->diagnostics.diagnostic));
740             
741             res->diagnostics.diagnostic[0]->code = error;
742             if (addinfo)
743             {
744                 res->diagnostics.diagnostic[0]->details =
745                     soap_malloc (soap, strlen(addinfo) + 1);
746                 strcpy (res->diagnostics.diagnostic[0]->details, addinfo);
747             }
748             else
749                 res->diagnostics.diagnostic[0]->details = 0;
750         }
751     }
752     else
753     {
754         if (s->m_r)
755         {
756             struct timeval tv;
757             const char *setname = ZOOM_resultset_option_get(s->m_r, "setname");
758             if (strcmp(setname, "default") && s->m_idle_time)
759             {
760                 res->resultSetId = soap_malloc(soap, strlen(setname));
761                 strcpy(res->resultSetId, setname);
762                 res->resultSetIdleTime = s->m_idle_time;
763                 gettimeofday(&tv, 0);
764                 s->m_expiry_sec = res->resultSetIdleTime + tv.tv_sec + 2;
765             } else {
766                 s->m_expiry_sec = 0;
767             }
768         }
769     }
770
771     if (t)
772     {
773         if (properties->optimize_level > 0)
774             target_leave(t);
775         else
776             target_destroy(t);
777     }
778     wrbuf_free(wr_log, 1);
779     if (error == ERROR_NO_TARGET)
780         return soap_receiver_fault(soap, "Cannot connect to Z39.50 target", 0);
781     return SOAP_OK;
782 }
783
784 int main(int argc, char **argv)
785 {
786     struct soap soap;
787     int ret;
788     int port = 0;
789     int no_threads = 40;
790     char *arg;
791     const char *host = 0;
792     struct srw_prop properties;
793
794     properties.optimize_level = 2;
795     properties.idle_time = 300;
796     properties.max_sets = 30;
797     properties.maps = 0;
798             
799     while ((ret = options("dO:T:l:hVp:s:x:i:", argv, argc, &arg)) != -2)
800     {
801         switch(ret)
802         {
803         case 0:
804             port = atoi(arg);
805             break;
806         case 'O':
807             properties.optimize_level = atoi(arg);
808             break;
809         case 'T':
810             no_threads = atoi(arg);
811             if (no_threads < 1 || no_threads > 200)
812                 no_threads = 40;
813             break;
814         case 's':
815             if (!properties.maps)
816             {
817                 properties.maps = xslt_maps_create();
818                 if (xslt_maps_file(properties.maps, arg))
819                 {
820                     fprintf (stderr, "maps file %s could not be opened\n",
821                              arg);
822                     exit(1);
823                 }
824             }
825             break;
826         case 'l':
827             yaz_log_init_file(arg);
828             break;
829         case 'V':
830             puts ("Version: $Id: srw-gateway.c,v 1.2 2003-01-20 13:04:50 adam Exp $"
831 #if SRW_DEBUG
832             " DEBUG"
833 #endif
834                 );
835             exit (0);
836         case 'p':
837             if (cql_transform_handle == 0)
838                 cql_transform_handle = cql_transform_open_fname(arg);
839             break;
840         case 'x':
841             properties.max_sets = atoi(arg);
842             break;
843         case 'i':
844             properties.idle_time = atoi(arg);
845             break;
846         case 'h':
847             printf ("srw-gateway [options] <port>\n");
848             printf ("  port  port for standalone service; If port is omitted, CGI is used.\n");
849             printf ("  -O n     optimize level. >= 1 cache connections, >=2 cache result sets.\n");
850 #if USE_THREADS
851             printf ("  -T n     number of threads.\n");
852 #else
853             printf ("  -T       unsupported in this version.\n");
854 #endif
855             printf ("  -l file  log to file (instead of stderr).\n");
856             printf ("  -p file  PQF properties.\n");
857             printf ("  -s file  schema maps.\n");
858             printf ("  -i time  idle time.\n");
859             printf ("  -x sets  live sets.\n");
860             printf ("  -V       show version.\n");
861             exit (1);
862         default:
863             fprintf (stderr, "srw-gateway: bad option -%s ; use -h for help\n",
864                      arg);
865             exit (1);
866             break;
867             
868         }
869     }
870     if (!cql_transform_handle)
871         cql_transform_handle = cql_transform_open_fname("pqf.properties");
872     if (!properties.maps)
873     {
874         properties.maps = xslt_maps_create();
875         xslt_maps_file(properties.maps, "maps.xml");
876     }
877     soap.encodingStyle = 0;
878     if (port == 0 && getenv("QUERY_STRING"))
879     {
880         properties.optimize_level = 0;
881
882         yaz_log_init_file("srw.log");
883         yaz_log (LOG_LOG, "CGI begin");
884         soap_init(&soap);
885         soap.user = &properties;
886
887         yaz_srw_serve(&soap, searchRetrieve, explain);
888
889         soap_end(&soap);
890         yaz_log (LOG_LOG, "CGI end");
891     }
892     else if (port)
893     {
894         if (!cql_transform_handle)
895         {
896             fprintf(stderr, "no properties file; use option -p to specify\n");
897             exit (1);
898         }
899         yaz_log (LOG_LOG, "standalone service on port %d", port);
900         
901         soap_init(&soap);
902
903         standalone(&soap, host, port, no_threads, &properties);
904     }
905     else
906     {
907         fprintf(stderr, "srw-gateway: no port specified. Use -h for help\n");
908     }
909     xslt_maps_free(properties.maps);
910     return 0;
911 }