1 /* This file is part of Metaproxy.
2 Copyright (C) Index Data
4 Metaproxy is free software; you can redistribute it and/or modify it under
5 the terms of the GNU General Public License as published by the Free
6 Software Foundation; either version 2, or (at your option) any later
9 Metaproxy is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 #include <metaproxy/package.hpp>
20 #include <metaproxy/util.hpp>
23 #include <yaz/diagbib1.h>
24 #include <yaz/match_glob.h>
25 #include <boost/scoped_ptr.hpp>
26 #include <boost/thread/mutex.hpp>
27 #include <boost/thread/condition.hpp>
32 namespace mp = metaproxy_1;
33 namespace yf = mp::filter;
35 namespace metaproxy_1 {
37 class SPARQL : public Base {
43 typedef boost::shared_ptr<Session> SessionPtr;
44 typedef boost::shared_ptr<Conf> ConfPtr;
46 typedef boost::shared_ptr<FrontendSet> FrontendSetPtr;
47 typedef std::map<std::string,FrontendSetPtr> FrontendSets;
51 void process(metaproxy_1::Package & package) const;
52 void configure(const xmlNode * ptr, bool test_only,
54 SessionPtr get_session(Package &package, Z_APDU **apdu) const;
55 void release_session(Package &package) const;
56 boost::scoped_ptr<Rep> m_p;
57 std::list<ConfPtr> db_conf;
69 boost::condition m_cond_session_ready;
71 std::map<mp::Session,SessionPtr> m_clients;
73 class SPARQL::FrontendSet {
84 class SPARQL::Session {
86 Session(const SPARQL *);
88 void handle_z(Package &package, Z_APDU *apdu);
89 Z_APDU *run_sparql(mp::Package &package,
92 const char *sparql_query,
96 ODR odr, Odr_oid *preferredRecordSyntax,
97 Z_ElementSetNames *esn,
98 int start, int number, int &error_code, std::string &addinfo,
99 int *number_returned, int *next_position);
102 bool m_support_named_result_sets;
103 FrontendSets m_frontend_sets;
104 const SPARQL *m_sparql;
109 yf::SPARQL::FrontendSet::~FrontendSet()
115 yf::SPARQL::FrontendSet::FrontendSet()
120 yf::SPARQL::SPARQL() : m_p(new Rep)
124 yf::SPARQL::~SPARQL()
128 void yf::SPARQL::configure(const xmlNode *xmlnode, bool test_only,
131 const xmlNode *ptr = xmlnode->children;
133 for (; ptr; ptr = ptr->next)
135 if (ptr->type != XML_ELEMENT_NODE)
137 if (!strcmp((const char *) ptr->name, "db"))
139 yaz_sparql_t s = yaz_sparql_create();
140 ConfPtr conf(new Conf);
143 const struct _xmlAttr *attr;
144 for (attr = ptr->properties; attr; attr = attr->next)
146 if (!strcmp((const char *) attr->name, "path"))
147 conf->db = mp::xml::get_text(attr->children);
148 else if (!strcmp((const char *) attr->name, "uri"))
149 conf->uri = mp::xml::get_text(attr->children);
150 else if (!strcmp((const char *) attr->name, "schema"))
151 conf->schema = mp::xml::get_text(attr->children);
153 throw mp::filter::FilterException(
154 "Bad attribute " + std::string((const char *)
157 xmlNode *p = ptr->children;
158 for (; p; p = p->next)
160 if (p->type != XML_ELEMENT_NODE)
162 std::string name = (const char *) p->name;
163 const struct _xmlAttr *attr;
164 for (attr = p->properties; attr; attr = attr->next)
166 if (!strcmp((const char *) attr->name, "type"))
169 name.append(mp::xml::get_text(attr->children));
172 throw mp::filter::FilterException(
173 "Bad attribute " + std::string((const char *)
176 std::string value = mp::xml::get_text(p);
177 if (yaz_sparql_add_pattern(s, name.c_str(), value.c_str()))
179 throw mp::filter::FilterException(
180 "Bad SPARQL config " + name);
183 if (!conf->uri.length())
185 throw mp::filter::FilterException("Missing uri");
187 if (!conf->db.length())
189 throw mp::filter::FilterException("Missing path");
191 db_conf.push_back(conf);
195 throw mp::filter::FilterException
197 + std::string((const char *) ptr->name)
198 + " in sparql filter");
203 yf::SPARQL::Conf::~Conf()
205 yaz_sparql_destroy(s);
208 yf::SPARQL::Session::Session(const SPARQL *sparql) :
210 m_support_named_result_sets(false),
215 yf::SPARQL::Session::~Session()
219 yf::SPARQL::SessionPtr yf::SPARQL::get_session(Package & package,
224 Z_GDU *gdu = package.request().get();
226 boost::mutex::scoped_lock lock(m_p->m_mutex);
228 std::map<mp::Session,SPARQL::SessionPtr>::iterator it;
230 if (gdu && gdu->which == Z_GDU_Z3950)
231 *apdu = gdu->u.z3950;
237 it = m_p->m_clients.find(package.session());
238 if (it == m_p->m_clients.end())
240 if (!it->second->m_in_use)
242 it->second->m_in_use = true;
245 m_p->m_cond_session_ready.wait(lock);
250 // new Z39.50 session ..
251 SessionPtr p(new Session(this));
252 m_p->m_clients[package.session()] = p;
256 void yf::SPARQL::release_session(Package &package) const
258 boost::mutex::scoped_lock lock(m_p->m_mutex);
259 std::map<mp::Session,SessionPtr>::iterator it;
261 it = m_p->m_clients.find(package.session());
262 if (it != m_p->m_clients.end())
264 it->second->m_in_use = false;
266 if (package.session().is_closed())
267 m_p->m_clients.erase(it);
268 m_p->m_cond_session_ready.notify_all();
272 static xmlNode *get_result(xmlDoc *doc, Odr_int *sz, Odr_int pos)
274 xmlNode *ptr = xmlDocGetRootElement(doc);
277 if (ptr->type == XML_ELEMENT_NODE &&
278 !strcmp((const char *) ptr->name, "RDF"))
282 while (ptr && ptr->type != XML_ELEMENT_NODE)
284 if (ptr && ptr->type == XML_ELEMENT_NODE &&
285 !strcmp((const char *) ptr->name, "Description"))
287 xmlNode *p = ptr->children;
289 while (p && p->type != XML_ELEMENT_NODE)
291 if (p && p->type == XML_ELEMENT_NODE &&
292 !strcmp((const char *) p->name, "type"))
293 { /* SELECT RESULT */
294 for (ptr = ptr->children; ptr; ptr = ptr->next)
295 if (ptr->type == XML_ELEMENT_NODE &&
296 !strcmp((const char *) ptr->name, "solution"))
303 { /* CONSTRUCT result */
304 for (; ptr; ptr = ptr->next)
305 if (ptr->type == XML_ELEMENT_NODE &&
306 !strcmp((const char *) ptr->name, "Description"))
316 for (; ptr; ptr = ptr->next)
317 if (ptr->type == XML_ELEMENT_NODE &&
318 !strcmp((const char *) ptr->name, "sparql"))
322 for (ptr = ptr->children; ptr; ptr = ptr->next)
323 if (ptr->type == XML_ELEMENT_NODE &&
324 !strcmp((const char *) ptr->name, "results"))
329 for (ptr = ptr->children; ptr; ptr = ptr->next)
330 if (ptr->type == XML_ELEMENT_NODE &&
331 !strcmp((const char *) ptr->name, "result"))
343 Z_Records *yf::SPARQL::Session::fetch(
345 ODR odr, Odr_oid *preferredRecordSyntax,
346 Z_ElementSetNames *esn,
347 int start, int number, int &error_code, std::string &addinfo,
348 int *number_returned, int *next_position)
350 Z_Records *rec = (Z_Records *) odr_malloc(odr, sizeof(Z_Records));
351 if (esn && esn->which == Z_ElementSetNames_generic &&
352 fset->conf->schema.length())
354 if (strcmp(esn->u.generic, fset->conf->schema.c_str()))
356 rec->which = Z_Records_NSD;
357 rec->u.nonSurrogateDiagnostic =
358 zget_DefaultDiagFormat(
360 YAZ_BIB1_SPECIFIED_ELEMENT_SET_NAME_NOT_VALID_FOR_SPECIFIED_,
365 rec->which = Z_Records_DBOSD;
366 rec->u.databaseOrSurDiagnostics = (Z_NamePlusRecordList *)
367 odr_malloc(odr, sizeof(Z_NamePlusRecordList));
368 rec->u.databaseOrSurDiagnostics->records = (Z_NamePlusRecord **)
369 odr_malloc(odr, sizeof(Z_NamePlusRecord *) * number);
371 for (i = 0; i < number; i++)
373 rec->u.databaseOrSurDiagnostics->records[i] = (Z_NamePlusRecord *)
374 odr_malloc(odr, sizeof(Z_NamePlusRecord));
375 Z_NamePlusRecord *npr = rec->u.databaseOrSurDiagnostics->records[i];
376 npr->databaseName = odr_strdup(odr, fset->db.c_str());
377 npr->which = Z_NamePlusRecord_databaseRecord;
379 xmlNode *node = get_result(fset->doc, 0, start - 1 + i);
382 assert(node->type == XML_ELEMENT_NODE);
383 xmlNode *tmp = xmlCopyNode(node, 1);
384 xmlBufferPtr buf = xmlBufferCreate();
385 xmlNodeDump(buf, tmp->doc, tmp, 0, 0);
386 npr->u.databaseRecord =
387 z_ext_record_xml(odr, (const char *) buf->content, buf->use);
391 rec->u.databaseOrSurDiagnostics->num_records = i;
392 *number_returned = i;
393 if (start + number > fset->hits)
396 *next_position = start + number;
400 Z_APDU *yf::SPARQL::Session::run_sparql(mp::Package &package,
403 const char *sparql_query,
406 Z_SearchRequest *req = apdu_req->u.searchRequest;
407 Package http_package(package.session(), package.origin());
409 http_package.copy_filter(package);
410 Z_GDU *gdu = z_get_HTTP_Request_uri(odr, conf->uri.c_str(), 0, 1);
412 z_HTTP_header_add(odr, &gdu->u.HTTP_Request->headers,
413 "Content-Type", "application/x-www-form-urlencoded");
414 z_HTTP_header_add(odr, &gdu->u.HTTP_Request->headers,
415 "Accept", "application/sparql-results+xml,"
416 "application/rdf+xml");
417 const char *names[2];
420 const char *values[1];
421 values[0] = sparql_query;
423 yaz_array_to_uri(&path, odr, (char **) names, (char **) values);
425 gdu->u.HTTP_Request->content_buf = path;
426 gdu->u.HTTP_Request->content_len = strlen(path);
428 yaz_log(YLOG_LOG, "sparql: HTTP request\n%s", sparql_query);
430 http_package.request() = gdu;
433 Z_GDU *gdu_resp = http_package.response().get();
434 Z_APDU *apdu_res = 0;
435 if (!gdu_resp || gdu_resp->which != Z_GDU_HTTP_Response)
437 yaz_log(YLOG_LOG, "sparql: no HTTP response");
438 apdu_res = odr.create_searchResponse(apdu_req,
439 YAZ_BIB1_TEMPORARY_SYSTEM_ERROR,
440 "no HTTP response from backend");
442 else if (gdu_resp->u.HTTP_Response->code != 200)
446 wrbuf_printf(w, "sparql: HTTP error %d from backend",
447 gdu_resp->u.HTTP_Response->code);
448 apdu_res = odr.create_searchResponse(apdu_req,
449 YAZ_BIB1_TEMPORARY_SYSTEM_ERROR,
454 Z_HTTP_Response *resp = gdu_resp->u.HTTP_Response;
455 FrontendSetPtr fset(new FrontendSet);
457 fset->doc = xmlParseMemory(resp->content_buf, resp->content_len);
458 fset->db = req->databaseNames[0];
461 apdu_res = odr.create_searchResponse(apdu_req,
462 YAZ_BIB1_TEMPORARY_SYSTEM_ERROR,
463 "invalid XML from backendbackend");
466 Z_Records *records = 0;
467 int number_returned = 0;
468 int next_position = 0;
472 get_result(fset->doc, &fset->hits, -1);
473 m_frontend_sets[req->resultSetName] = fset;
476 const char *element_set_name = 0;
477 mp::util::piggyback_sr(req, fset->hits, number, &element_set_name);
480 Z_ElementSetNames *esn;
482 if (number > *req->smallSetUpperBound)
483 esn = req->mediumSetElementSetNames;
485 esn = req->smallSetElementSetNames;
486 records = fetch(fset,
487 odr, req->preferredRecordSyntax, esn,
496 odr.create_searchResponse(
497 apdu_req, error_code, addinfo.c_str());
502 odr.create_searchResponse(apdu_req, 0, 0);
503 Z_SearchResponse *resp = apdu_res->u.searchResponse;
504 *resp->resultCount = fset->hits;
505 *resp->numberOfRecordsReturned = number_returned;
506 *resp->nextResultSetPosition = next_position;
507 resp->records = records;
514 void yf::SPARQL::Session::handle_z(mp::Package &package, Z_APDU *apdu_req)
517 Z_APDU *apdu_res = 0;
518 if (apdu_req->which == Z_APDU_initRequest)
520 apdu_res = odr.create_initResponse(apdu_req, 0, 0);
521 Z_InitRequest *req = apdu_req->u.initRequest;
522 Z_InitResponse *resp = apdu_res->u.initResponse;
524 resp->implementationName = odr_strdup(odr, "sparql");
525 if (ODR_MASK_GET(req->options, Z_Options_namedResultSets))
526 m_support_named_result_sets = true;
528 static const int masks[] = {
529 Z_Options_search, Z_Options_present,
530 Z_Options_namedResultSets, -1
532 for (i = 0; masks[i] != -1; i++)
533 if (ODR_MASK_GET(req->options, masks[i]))
534 ODR_MASK_SET(resp->options, masks[i]);
535 static const int versions[] = {
541 for (i = 0; versions[i] != -1; i++)
542 if (ODR_MASK_GET(req->protocolVersion, versions[i]))
543 ODR_MASK_SET(resp->protocolVersion, versions[i]);
546 *resp->preferredMessageSize = *req->preferredMessageSize;
547 *resp->maximumRecordSize = *req->maximumRecordSize;
549 else if (apdu_req->which == Z_APDU_close)
551 apdu_res = odr.create_close(apdu_req,
552 Z_Close_finished, 0);
553 package.session().close();
555 else if (apdu_req->which == Z_APDU_searchRequest)
557 Z_SearchRequest *req = apdu_req->u.searchRequest;
559 FrontendSets::iterator fset_it =
560 m_frontend_sets.find(req->resultSetName);
561 if (fset_it != m_frontend_sets.end())
563 // result set already exist
564 // if replace indicator is off: we return diagnostic if
565 // result set already exist.
566 if (*req->replaceIndicator == 0)
569 odr.create_searchResponse(
571 YAZ_BIB1_RESULT_SET_EXISTS_AND_REPLACE_INDICATOR_OFF,
573 package.response() = apdu_res;
575 m_frontend_sets.erase(fset_it);
577 if (req->query->which != Z_Query_type_1)
579 apdu_res = odr.create_searchResponse(
580 apdu_req, YAZ_BIB1_QUERY_TYPE_UNSUPP, 0);
582 else if (req->num_databaseNames != 1)
584 apdu_res = odr.create_searchResponse(
586 YAZ_BIB1_ACCESS_TO_SPECIFIED_DATABASE_DENIED, 0);
590 std::string db = req->databaseNames[0];
591 std::list<ConfPtr>::const_iterator it;
593 it = m_sparql->db_conf.begin();
594 for (; it != m_sparql->db_conf.end(); it++)
595 if (yaz_match_glob((*it)->db.c_str(), db.c_str()))
597 if (it == m_sparql->db_conf.end())
599 apdu_res = odr.create_searchResponse(
600 apdu_req, YAZ_BIB1_DATABASE_DOES_NOT_EXIST, db.c_str());
604 WRBUF addinfo_wr = wrbuf_alloc();
605 WRBUF sparql_wr = wrbuf_alloc();
607 yaz_sparql_from_rpn_wrbuf((*it)->s,
608 addinfo_wr, sparql_wr,
609 req->query->u.type_1);
612 apdu_res = odr.create_searchResponse(
614 wrbuf_len(addinfo_wr) ?
615 wrbuf_cstr(addinfo_wr) : 0);
619 apdu_res = run_sparql(package, apdu_req, odr,
620 wrbuf_cstr(sparql_wr), *it);
622 wrbuf_destroy(addinfo_wr);
623 wrbuf_destroy(sparql_wr);
627 else if (apdu_req->which == Z_APDU_presentRequest)
629 Z_PresentRequest *req = apdu_req->u.presentRequest;
630 FrontendSets::iterator fset_it =
631 m_frontend_sets.find(req->resultSetId);
632 if (fset_it == m_frontend_sets.end())
635 odr.create_presentResponse(
636 apdu_req, YAZ_BIB1_SPECIFIED_RESULT_SET_DOES_NOT_EXIST,
638 package.response() = apdu_res;
641 int number_returned = 0;
642 int next_position = 0;
645 Z_ElementSetNames *esn = 0;
646 if (req->recordComposition)
648 if (req->recordComposition->which == Z_RecordComp_simple)
649 esn = req->recordComposition->u.simple;
653 odr.create_presentResponse(
655 YAZ_BIB1_ONLY_A_SINGLE_ELEMENT_SET_NAME_SUPPORTED,
657 package.response() = apdu_res;
661 Z_Records *records = fetch(
663 odr, req->preferredRecordSyntax, esn,
664 *req->resultSetStartPoint, *req->numberOfRecordsRequested,
671 odr.create_presentResponse(apdu_req, error_code,
677 odr.create_presentResponse(apdu_req, 0, 0);
678 Z_PresentResponse *resp = apdu_res->u.presentResponse;
679 resp->records = records;
680 *resp->numberOfRecordsReturned = number_returned;
681 *resp->nextResultSetPosition = next_position;
686 apdu_res = odr.create_close(apdu_req,
687 Z_Close_protocolError,
688 "sparql: unhandled APDU");
689 package.session().close();
693 package.response() = apdu_res;
696 void yf::SPARQL::process(mp::Package &package) const
699 SessionPtr p = get_session(package, &apdu);
702 p->handle_z(package, apdu);
706 release_session(package);
709 static mp::filter::Base* filter_creator()
711 return new mp::filter::SPARQL;
715 struct metaproxy_1_filter_struct metaproxy_1_filter_sparql = {
726 * c-file-style: "Stroustrup"
727 * indent-tabs-mode: nil
729 * vim: shiftwidth=4 tabstop=8 expandtab