Deliver recordSchema in response
[yazpp-moved-to-github.git] / src / yaz-proxy.cpp
index 71dd336..4b280f7 100644 (file)
@@ -1,8 +1,8 @@
 /*
- * Copyright (c) 1998-2003, Index Data.
+ * Copyright (c) 1998-2004, Index Data.
  * See the file LICENSE for details.
  * 
- * $Id: yaz-proxy.cpp,v 1.72 2003-12-20 22:44:30 adam Exp $
+ * $Id: yaz-proxy.cpp,v 1.78 2004-01-07 11:10:55 adam Exp $
  */
 
 #include <assert.h>
 #include <yaz++/proxy.h>
 #include <yaz/pquery.h>
 
+#if HAVE_XSLT
+#include <libxslt/xsltutils.h>
+#include <libxslt/transform.h>
+#endif
+
 static const char *apdu_name(Z_APDU *apdu)
 {
     switch (apdu->which)
@@ -101,6 +106,8 @@ Yaz_Proxy::Yaz_Proxy(IYaz_PDU_Observable *the_PDU_Observable,
     m_invalid_session = 0;
     m_config = 0;
     m_marcxml_flag = 0;
+    m_stylesheet = 0;
+    m_schema = 0;
     m_initRequest_apdu = 0;
     m_initRequest_mem = 0;
     m_apdu_invalid_session = 0;
@@ -114,7 +121,8 @@ Yaz_Proxy::Yaz_Proxy(IYaz_PDU_Observable *the_PDU_Observable,
     m_http_version = 0;
     m_soap_ns = 0;
     m_s2z_packing = Z_SRW_recordPacking_string;
-    m_zeerex_fname = 0;
+    m_time_tv.tv_sec = 0;
+    m_time_tv.tv_usec = 0;
 }
 
 Yaz_Proxy::~Yaz_Proxy()
@@ -127,6 +135,8 @@ Yaz_Proxy::~Yaz_Proxy()
     xfree (m_default_target);
     xfree (m_proxy_authentication);
     xfree (m_optimize);
+    xfree (m_stylesheet);
+    xfree (m_schema);
     if (m_s2z_odr_init)
        odr_destroy(m_s2z_odr_init);
     if (m_s2z_odr_search)
@@ -314,8 +324,7 @@ Yaz_ProxyClient *Yaz_Proxy::get_client(Z_APDU *apdu, const char *cookie,
                                 &m_keepalive_limit_bw,
                                 &m_keepalive_limit_pdu,
                                 &pre_init,
-                                &cql2rpn_fname,
-                                &m_zeerex_fname);
+                                &cql2rpn_fname);
        }
        if (client_idletime != -1)
        {
@@ -572,6 +581,44 @@ void Yaz_Proxy::display_diagrecs(Z_DiagRec **pp, int num)
     }
 }
 
+void Yaz_Proxy::convert_xsl(Z_NamePlusRecordList *p)
+{
+    if (!m_stylesheet)
+       return;
+    xmlDocPtr xslt_doc = xmlParseFile(m_stylesheet);
+    xsltStylesheetPtr xsp;
+
+    xsp = xsltParseStylesheetDoc(xslt_doc);
+
+    int i;
+    for (i = 0; i < p->num_records; i++)
+    {
+       Z_NamePlusRecord *npr = p->records[i];
+       if (npr->which == Z_NamePlusRecord_databaseRecord)
+       {
+           Z_External *r = npr->u.databaseRecord;
+           if (r->which == Z_External_octet)
+           {
+               xmlDocPtr res, doc = xmlParseMemory(
+                   (char*) r->u.octet_aligned->buf,
+                   r->u.octet_aligned->len);
+               
+               res = xsltApplyStylesheet(xsp, doc, 0);
+               
+               xmlChar *out_buf;
+               int out_len;
+               xmlDocDumpMemory (res, &out_buf, &out_len);
+               p->records[i]->u.databaseRecord = 
+                   z_ext_record(odr_encode(), VAL_TEXT_XML,
+                                (char*) out_buf, out_len);
+               xmlFreeDoc(doc);
+               xmlFreeDoc(res);
+           }
+       }
+    }
+    xsltFreeStylesheet(xsp);
+}
+
 void Yaz_Proxy::convert_to_marcxml(Z_NamePlusRecordList *p)
 {
     int i;
@@ -633,6 +680,22 @@ void Yaz_Proxy::convert_to_marcxml(Z_NamePlusRecordList *p)
     yaz_marc_destroy(mt);
 }
 
+void Yaz_Proxy::logtime()
+{
+    if (m_time_tv.tv_sec)
+    {
+       struct timeval tv;
+       gettimeofday(&tv, 0);
+       long diff = (tv.tv_sec - m_time_tv.tv_sec)*1000000 +
+           (tv.tv_usec - m_time_tv.tv_usec);
+       if (diff >= 0)
+           yaz_log(LOG_LOG, "%sElapsed %ld.%03ld", m_session_str,
+                   diff/1000000, (diff/1000)%1000);
+    }
+    m_time_tv.tv_sec = 0;
+    m_time_tv.tv_usec = 0;
+}
+
 int Yaz_Proxy::send_http_response(int code)
 {
     ODR o = odr_encode();
@@ -642,8 +705,15 @@ int Yaz_Proxy::send_http_response(int code)
     if (m_http_version)
        hres->version = odr_strdup(o, m_http_version);
     m_http_keepalive = 0;
+    if (m_log_mask & PROXY_LOG_REQ_CLIENT)
+    {
+       yaz_log (LOG_LOG, "%sSending %s to client", m_session_str,
+                gdu_name(gdu));
+    }
     int len;
-    return send_GDU(gdu, &len);
+    int r = send_GDU(gdu, &len);
+    logtime();
+    return r;
 }
 
 int Yaz_Proxy::send_srw_response(Z_SRW_PDU *srw_pdu)
@@ -659,7 +729,7 @@ int Yaz_Proxy::send_srw_response(Z_SRW_PDU *srw_pdu)
         z_HTTP_header_add(o, &hres->headers, "Connection", "Keep-Alive");
 
     static Z_SOAP_Handler soap_handlers[2] = {
-#if HAVE_XML2
+#if HAVE_XSLT
        {"http://www.loc.gov/zing/srw/", 0,
         (Z_SOAP_fun) yaz_srw_codec},
 #endif
@@ -677,8 +747,15 @@ int Yaz_Proxy::send_srw_response(Z_SRW_PDU *srw_pdu)
     int ret = z_soap_codec_enc(o, &soap_package,
                               &hres->content_buf, &hres->content_len,
                               soap_handlers, 0);
+    if (m_log_mask & PROXY_LOG_REQ_CLIENT)
+    {
+       yaz_log (LOG_LOG, "%sSending %s to client", m_session_str,
+                gdu_name(gdu));
+    }
     int len;
-    return send_GDU(gdu, &len);
+    int r = send_GDU(gdu, &len);
+    logtime();
+    return r;
 }
 
 int Yaz_Proxy::send_to_srw_client_error(int srw_error)
@@ -740,7 +817,7 @@ int Yaz_Proxy::send_to_srw_client_ok(int hits, Z_Records *records, int start)
            oident *ent = oid_getentbyoid(r->direct_reference);
            if (r->which == Z_External_octet && ent->value == VAL_TEXT_XML)
            {
-               srw_res->records[i].recordSchema = "http://www.loc.gov/marcxml/";
+               srw_res->records[i].recordSchema = m_schema;
                srw_res->records[i].recordPacking = m_s2z_packing;
                srw_res->records[i].recordData_buf = (char*) 
                    r->u.octet_aligned->buf;
@@ -774,31 +851,20 @@ int Yaz_Proxy::send_srw_explain()
     Z_SRW_PDU *res = yaz_srw_get(odr_encode(), Z_SRW_explain_response);
     Z_SRW_explainResponse *er = res->u.explain_response;
     
-    if (m_zeerex_fname)
+    Yaz_ProxyConfig *cfg = check_reconfigure();
+    if (cfg)
     {
-       FILE *inf = fopen(m_zeerex_fname, "rb");
-       if (inf)
+       int len;
+       assert (m_proxyTarget);
+       char *b = cfg->get_explain(odr_encode(), 0 /* target */,
+                                  0 /* db */, &len);
+       if (b)
        {
-           fseek(inf, 0L, SEEK_END);
-           long sz = ftell(inf);
-           fseek(inf, 0L, SEEK_SET);
-           if (sz > 0)
-           {
-               er->record.recordData_buf = 
-                   (char*) odr_malloc(odr_encode(), sz);
-               size_t s = fread(er->record.recordData_buf, 1,sz, inf);
-               if (s > 0)
-                   er->record.recordData_len = s;
-           }
-           else
-               yaz_log(LOG_WARN|LOG_ERRNO, "zeerex file: ftell");
-           fclose(inf);
+           er->record.recordData_buf = b;
+           er->record.recordData_len = len;
+           er->record.recordPacking = m_s2z_packing;
        }
-       else
-           yaz_log(LOG_WARN|LOG_ERRNO, "zeerex file: fopen");
     }
-    else
-       yaz_log(LOG_LOG, "zeerex file: not defined");
     return send_srw_response(res);
 }
 
@@ -851,7 +917,14 @@ int Yaz_Proxy::send_PDU_convert(Z_APDU *apdu, int *len)
        }
     }
     else
-       return send_Z_PDU(apdu, len);
+    {
+       if (m_log_mask & PROXY_LOG_REQ_CLIENT)
+           yaz_log (LOG_LOG, "%sSending %s to client", m_session_str,
+                    apdu_name(apdu));
+       int r = send_Z_PDU(apdu, len);
+       logtime();
+       return r;
+    }
     return 0;
 }
 
@@ -874,8 +947,12 @@ int Yaz_Proxy::send_to_client(Z_APDU *apdu)
        }
        else
        {
-           if (m_marcxml_flag && p && p->which == Z_Records_DBOSD)
-               convert_to_marcxml(p->u.databaseOrSurDiagnostics);
+           if (p && p->which == Z_Records_DBOSD)
+           {
+               if (m_marcxml_flag)
+                   convert_to_marcxml(p->u.databaseOrSurDiagnostics);
+               convert_xsl(p->u.databaseOrSurDiagnostics);
+           }
            if (sr->resultCount)
            {
                yaz_log(LOG_LOG, "%s%d hits", m_session_str,
@@ -906,15 +983,16 @@ int Yaz_Proxy::send_to_client(Z_APDU *apdu)
                *sr->presentStatus = Z_PresentStatus_failure;
            display_diagrecs(&dr_p, 1);
        }
-       if (m_marcxml_flag && p && p->which == Z_Records_DBOSD)
-           convert_to_marcxml(p->u.databaseOrSurDiagnostics);
+       if (p && p->which == Z_Records_DBOSD)
+       {
+           if (m_marcxml_flag)
+               convert_to_marcxml(p->u.databaseOrSurDiagnostics);
+           convert_xsl(p->u.databaseOrSurDiagnostics);
+       }
     }
     int r = send_PDU_convert(apdu, &len);
     if (r)
        return r;
-    if (m_log_mask & PROXY_LOG_APDU_CLIENT)
-       yaz_log (LOG_DEBUG, "%sSending %s to client %d bytes", m_session_str,
-                apdu_name(apdu), len);
     m_bytes_sent += len;
     m_bw_stat.add_bytes(len);
     if (kill_session)
@@ -1178,12 +1256,17 @@ Z_APDU *Yaz_Proxy::result_set_optimize(Z_APDU *apdu)
 }
 
 
-void Yaz_Proxy::recv_GDU(Z_GDU *apdu, int len)
+void Yaz_Proxy::inc_request_no()
 {
     char *cp = strchr(m_session_str, ' ');
     m_request_no++;
     if (cp)
        sprintf(cp+1, "%d ", m_request_no);
+}
+
+void Yaz_Proxy::recv_GDU(Z_GDU *apdu, int len)
+{
+    inc_request_no();
 
     m_bytes_recv += len;
     
@@ -1197,6 +1280,8 @@ void Yaz_Proxy::recv_GDU(Z_GDU *apdu, int len)
     m_bw_stat.add_bytes(len);
     m_pdu_stat.add_bytes(1);
 
+    gettimeofday(&m_time_tv, 0);
+
     int bw_total = m_bw_stat.get_total();
     int pdu_total = m_pdu_stat.get_total();
 
@@ -1277,16 +1362,32 @@ Z_APDU *Yaz_Proxy::handle_query_transformation(Z_APDU *apdu)
     {
        Z_RPNQuery *rpnquery = 0;
        Z_SearchRequest *sr = apdu->u.searchRequest;
+       char *addinfo = 0;
        
        yaz_log(LOG_LOG, "%sCQL: %s", m_session_str,
                sr->query->u.type_104->u.cql);
 
        int r = m_cql2rpn.query_transform(sr->query->u.type_104->u.cql,
-                                         &rpnquery, odr_encode());
+                                         &rpnquery, odr_encode(),
+                                         &addinfo);
        if (r == -3)
            yaz_log(LOG_LOG, "%sNo CQL to RPN table", m_session_str);
        else if (r)
+       {
            yaz_log(LOG_LOG, "%sCQL Conversion error %d", m_session_str, r);
+           Z_APDU *new_apdu = create_Z_PDU(Z_APDU_searchResponse);
+
+           new_apdu->u.searchResponse->referenceId = sr->referenceId;
+           new_apdu->u.searchResponse->records =
+               create_nonSurrogateDiagnostics(odr_encode(),
+                                              yaz_diag_srw_to_bib1(r),
+                                              addinfo);
+           *new_apdu->u.searchResponse->searchStatus = 0;
+
+           send_to_client(new_apdu);
+
+           return 0;
+       }
        else
        {
            sr->query->which = Z_Query_type_1;
@@ -1335,17 +1436,24 @@ Z_APDU *Yaz_Proxy::handle_syntax_validation(Z_APDU *apdu)
        int err = 0;
        char *addinfo = 0;
        Yaz_ProxyConfig *cfg = check_reconfigure();
-       
+
+       Z_RecordComposition rc_temp, *rc = 0;
+       if (sr->smallSetElementSetNames)
+       {
+           rc_temp.which = Z_RecordComp_simple;
+           rc_temp.u.simple = sr->smallSetElementSetNames;
+           rc = &rc_temp;
+       }
+           
        if (cfg)
            err = cfg->check_syntax(odr_encode(),
                                    m_default_target,
-                                   sr->preferredRecordSyntax,
-                                   &addinfo);
+                                   sr->preferredRecordSyntax, rc,
+                                   &addinfo, &m_stylesheet, &m_schema);
        if (err == -1)
        {
            sr->preferredRecordSyntax =
-               yaz_oidval_to_z3950oid(odr_decode(), CLASS_RECSYN,
-                                      VAL_USMARC);
+               yaz_oidval_to_z3950oid(odr_encode(), CLASS_RECSYN, VAL_USMARC);
            m_marcxml_flag = 1;
        }
        else if (err)
@@ -1372,12 +1480,12 @@ Z_APDU *Yaz_Proxy::handle_syntax_validation(Z_APDU *apdu)
        if (cfg)
            err = cfg->check_syntax(odr_encode(), m_default_target,
                                    pr->preferredRecordSyntax,
-                                   &addinfo);
+                                   pr->recordComposition,
+                                   &addinfo, &m_stylesheet, &m_schema);
        if (err == -1)
        {
            pr->preferredRecordSyntax =
-               yaz_oidval_to_z3950oid(odr_decode(), CLASS_RECSYN,
-                                      VAL_USMARC);
+               yaz_oidval_to_z3950oid(odr_decode(), CLASS_RECSYN, VAL_USMARC);
            m_marcxml_flag = 1;
        }
        else if (err)
@@ -1398,10 +1506,20 @@ Z_APDU *Yaz_Proxy::handle_syntax_validation(Z_APDU *apdu)
     return apdu;
 }
 
+Z_ElementSetNames *Yaz_Proxy::mk_esn_from_schema(ODR o, const char *schema)
+{
+    if (!schema)
+       return 0;
+    Z_ElementSetNames *esn = (Z_ElementSetNames *)
+       odr_malloc(o, sizeof(Z_ElementSetNames));
+    esn->which = Z_ElementSetNames_generic;
+    esn->u.generic = odr_strdup(o, schema);
+    return esn;
+}
+
 void Yaz_Proxy::handle_incoming_HTTP(Z_HTTP_Request *hreq)
 {
-    Z_SRW_PDU *srw_pdu = 0;
-    char *soap_ns = 0;
+
     if (m_s2z_odr_init)
     {
        odr_destroy(m_s2z_odr_init);
@@ -1434,12 +1552,17 @@ void Yaz_Proxy::handle_incoming_HTTP(Z_HTTP_Request *hreq)
         m_http_version = "1.1";
     }
 
-    if (yaz_check_for_srw(hreq, &srw_pdu, &soap_ns, odr_decode()) == 0
-       || yaz_check_for_sru(hreq, &srw_pdu, &soap_ns, odr_decode()) == 0)
+    Z_SRW_PDU *srw_pdu = 0;
+    Z_SOAP *soap_package = 0;
+    char *charset = 0;
+    if (yaz_srw_decode(hreq, &srw_pdu, &soap_package, odr_decode(),
+                      &charset) == 0
+       || yaz_sru_decode(hreq, &srw_pdu, &soap_package, odr_decode(),
+                         &charset) == 0)
     {
        m_s2z_odr_init = odr_createmem(ODR_ENCODE);
        m_s2z_odr_search = odr_createmem(ODR_ENCODE);
-       m_soap_ns = odr_strdup(m_s2z_odr_search, soap_ns);
+       m_soap_ns = odr_strdup(m_s2z_odr_search, soap_package->ns);
        m_s2z_init_apdu = 0;
        m_s2z_search_apdu = 0;
        m_s2z_present_apdu = 0;
@@ -1526,14 +1649,24 @@ void Yaz_Proxy::handle_incoming_HTTP(Z_HTTP_Request *hreq)
                start = *srw_req->startRecord;
            if (max > 0)
            {
-               if (start <= 1)  // Z39.50 piggyback
+                // Some backend, such as Voyager doesn't honor piggyback
+               // So we use present always (0 &&).
+               if (0 && start <= 1)  // Z39.50 piggyback
                {
                    *z_searchRequest->smallSetUpperBound = max;
                    *z_searchRequest->mediumSetPresentNumber = max;
                    *z_searchRequest->largeSetLowerBound = 2000000000; // 2e9
+
                    z_searchRequest->preferredRecordSyntax =
                        yaz_oidval_to_z3950oid(m_s2z_odr_search, CLASS_RECSYN,
                                               VAL_TEXT_XML);
+                   if (srw_req->recordSchema)
+                   {
+                       z_searchRequest->smallSetElementSetNames =
+                           z_searchRequest->mediumSetElementSetNames =
+                           mk_esn_from_schema(m_s2z_odr_search,
+                                              srw_req->recordSchema);
+                   }
                }
                else   // Z39.50 present
                {
@@ -1546,11 +1679,22 @@ void Yaz_Proxy::handle_incoming_HTTP(Z_HTTP_Request *hreq)
                    z_presentRequest->preferredRecordSyntax =
                        yaz_oidval_to_z3950oid(m_s2z_odr_search, CLASS_RECSYN,
                                               VAL_TEXT_XML);
+                   z_presentRequest->recordComposition =
+                       (Z_RecordComposition *)
+                       odr_malloc(m_s2z_odr_search,
+                                  sizeof(Z_RecordComposition));
+                   if (srw_req->recordSchema)
+                   {
+                       z_presentRequest->recordComposition->which = 
+                           Z_RecordComp_simple;                    
+                       z_presentRequest->recordComposition->u.simple =
+                           mk_esn_from_schema(m_s2z_odr_search,
+                                              srw_req->recordSchema);
+                   }
                }
            }
            if (!m_client)
            {
-               yaz_log(LOG_LOG, "handle_incoming: initRequest");
                m_s2z_init_apdu = zget_APDU(m_s2z_odr_init,
                                            Z_APDU_initRequest);
                
@@ -1568,6 +1712,14 @@ void Yaz_Proxy::handle_incoming_HTTP(Z_HTTP_Request *hreq)
        }
        else if (srw_pdu->which == Z_SRW_explain_request)
        {
+           Z_SRW_explainRequest *srw_req = srw_pdu->u.explain_request;
+           
+           if (srw_req->recordPacking &&
+               !strcmp(srw_req->recordPacking, "xml"))
+               m_s2z_packing = Z_SRW_recordPacking_XML;
+           else
+               m_s2z_packing = Z_SRW_recordPacking_string;
+
            if (!m_client)
            {
                yaz_log(LOG_LOG, "handle_incoming: initRequest");
@@ -1754,6 +1906,7 @@ void Yaz_ProxyClient::shutdown()
 
 void Yaz_Proxy::failNotify()
 {
+    inc_request_no();
     yaz_log (LOG_LOG, "%sConnection closed by client",
             get_session_str());
     shutdown();
@@ -1761,6 +1914,8 @@ void Yaz_Proxy::failNotify()
 
 void Yaz_ProxyClient::failNotify()
 {
+    if (m_server)
+       m_server->inc_request_no();
     yaz_log (LOG_LOG, "%sConnection closed by target %s", 
             get_session_str(), get_hostname());
     shutdown();
@@ -1854,8 +2009,7 @@ void Yaz_Proxy::pre_init()
                                          &keepalive_limit_bw,
                                          &keepalive_limit_pdu,
                                          &pre_init,
-                                         &cql2rpn,
-                                         &zeerex) ; i++)
+                                         &cql2rpn) ; i++)
     {
        if (pre_init)
        {
@@ -1931,6 +2085,8 @@ void Yaz_Proxy::timeoutNotify()
        }
        else
        {
+           inc_request_no();
+
            yaz_log (LOG_LOG, "%sTimeout (client to proxy)", m_session_str);
            shutdown();
        }
@@ -1944,6 +2100,9 @@ void Yaz_Proxy::timeoutNotify()
 
 void Yaz_ProxyClient::timeoutNotify()
 {
+    if (m_server)
+       m_server->inc_request_no();
+
     yaz_log (LOG_LOG, "%sTimeout (proxy to target) %s", get_session_str(),
             get_hostname());
     m_waiting = 1;
@@ -2109,7 +2268,7 @@ int Yaz_Proxy::server(const char *addr)
     int r = Yaz_Z_Assoc::server(addr);
     if (!r)
     {
-       yaz_log(LOG_LOG, "%sStarted listener on %s", m_session_str, addr);
+       yaz_log(LOG_LOG, "%sStarted proxy " VERSION " on %s", m_session_str, addr);
        timeout(1);
     }
     return r;