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;
68 boost::condition m_cond_session_ready;
70 std::map<mp::Session,SessionPtr> m_clients;
72 class SPARQL::FrontendSet {
81 class SPARQL::Session {
83 Session(const SPARQL *);
85 void handle_z(Package &package, Z_APDU *apdu);
86 Z_APDU *run_sparql(mp::Package &package,
89 const char *sparql_query,
93 bool m_support_named_result_sets;
94 FrontendSets m_frontend_sets;
95 const SPARQL *m_sparql;
100 yf::SPARQL::FrontendSet::~FrontendSet()
106 yf::SPARQL::FrontendSet::FrontendSet()
111 yf::SPARQL::SPARQL() : m_p(new Rep)
115 yf::SPARQL::~SPARQL()
119 void yf::SPARQL::configure(const xmlNode *xmlnode, bool test_only,
122 const xmlNode *ptr = xmlnode->children;
124 for (; ptr; ptr = ptr->next)
126 if (ptr->type != XML_ELEMENT_NODE)
128 if (!strcmp((const char *) ptr->name, "db"))
130 yaz_sparql_t s = yaz_sparql_create();
131 ConfPtr conf(new Conf);
134 const struct _xmlAttr *attr;
135 for (attr = ptr->properties; attr; attr = attr->next)
137 if (!strcmp((const char *) attr->name, "path"))
138 conf->db = mp::xml::get_text(attr->children);
139 else if (!strcmp((const char *) attr->name, "uri"))
140 conf->uri = mp::xml::get_text(attr->children);
142 throw mp::filter::FilterException(
143 "Bad attribute " + std::string((const char *)
146 xmlNode *p = ptr->children;
147 for (; p; p = p->next)
149 if (p->type != XML_ELEMENT_NODE)
151 std::string name = (const char *) p->name;
152 const struct _xmlAttr *attr;
153 for (attr = p->properties; attr; attr = attr->next)
155 if (!strcmp((const char *) attr->name, "type"))
158 name.append(mp::xml::get_text(attr->children));
161 throw mp::filter::FilterException(
162 "Bad attribute " + std::string((const char *)
165 std::string value = mp::xml::get_text(p);
166 if (yaz_sparql_add_pattern(s, name.c_str(), value.c_str()))
168 throw mp::filter::FilterException(
169 "Bad SPARQL config " + name);
172 if (!conf->uri.length())
174 throw mp::filter::FilterException("Missing uri");
176 if (!conf->db.length())
178 throw mp::filter::FilterException("Missing path");
180 db_conf.push_back(conf);
184 throw mp::filter::FilterException
186 + std::string((const char *) ptr->name)
187 + " in sparql filter");
192 yf::SPARQL::Conf::~Conf()
194 yaz_sparql_destroy(s);
197 yf::SPARQL::Session::Session(const SPARQL *sparql) :
199 m_support_named_result_sets(false),
204 yf::SPARQL::Session::~Session()
208 yf::SPARQL::SessionPtr yf::SPARQL::get_session(Package & package,
213 Z_GDU *gdu = package.request().get();
215 boost::mutex::scoped_lock lock(m_p->m_mutex);
217 std::map<mp::Session,SPARQL::SessionPtr>::iterator it;
219 if (gdu && gdu->which == Z_GDU_Z3950)
220 *apdu = gdu->u.z3950;
226 it = m_p->m_clients.find(package.session());
227 if (it == m_p->m_clients.end())
229 if (!it->second->m_in_use)
231 it->second->m_in_use = true;
234 m_p->m_cond_session_ready.wait(lock);
239 // new Z39.50 session ..
240 SessionPtr p(new Session(this));
241 m_p->m_clients[package.session()] = p;
245 void yf::SPARQL::release_session(Package &package) const
247 boost::mutex::scoped_lock lock(m_p->m_mutex);
248 std::map<mp::Session,SessionPtr>::iterator it;
250 it = m_p->m_clients.find(package.session());
251 if (it != m_p->m_clients.end())
253 it->second->m_in_use = false;
255 if (package.session().is_closed())
256 m_p->m_clients.erase(it);
257 m_p->m_cond_session_ready.notify_all();
261 Z_APDU *yf::SPARQL::Session::run_sparql(mp::Package &package,
264 const char *sparql_query,
267 Package http_package(package.session(), package.origin());
269 http_package.copy_filter(package);
270 Z_GDU *gdu = z_get_HTTP_Request_uri(odr, uri, 0, 1);
272 z_HTTP_header_add(odr, &gdu->u.HTTP_Request->headers,
273 "Content-Type", "application/x-www-form-urlencoded");
274 const char *names[2];
277 const char *values[1];
278 values[0] = sparql_query;
280 yaz_array_to_uri(&path, odr, (char **) names, (char **) values);
282 gdu->u.HTTP_Request->content_buf = path;
283 gdu->u.HTTP_Request->content_len = strlen(path);
285 yaz_log(YLOG_LOG, "sparql: HTTP request\n%s", sparql_query);
287 http_package.request() = gdu;
290 Z_GDU *gdu_resp = http_package.response().get();
291 Z_APDU *apdu_res = 0;
292 if (gdu_resp && gdu_resp->which == Z_GDU_HTTP_Response)
294 Z_HTTP_Response *resp = gdu_resp->u.HTTP_Response;
295 FrontendSetPtr fset(new FrontendSet);
297 fset->doc = xmlParseMemory(resp->content_buf, resp->content_len);
299 apdu_res = odr.create_searchResponse(apdu_req,
300 YAZ_BIB1_TEMPORARY_SYSTEM_ERROR,
301 "invalid XML from backendbackend");
304 apdu_res = odr.create_searchResponse(apdu_req, 0, 0);
306 m_frontend_sets[apdu_req->u.searchRequest->resultSetName] = fset;
311 yaz_log(YLOG_LOG, "sparql: no HTTP response");
312 apdu_res = odr.create_searchResponse(apdu_req,
313 YAZ_BIB1_TEMPORARY_SYSTEM_ERROR,
314 "no HTTP response from backend");
319 void yf::SPARQL::Session::handle_z(mp::Package &package, Z_APDU *apdu_req)
322 Z_APDU *apdu_res = 0;
323 if (apdu_req->which == Z_APDU_initRequest)
325 apdu_res = odr.create_initResponse(apdu_req, 0, 0);
326 Z_InitRequest *req = apdu_req->u.initRequest;
327 Z_InitResponse *resp = apdu_res->u.initResponse;
329 resp->implementationName = odr_strdup(odr, "sparql");
330 if (ODR_MASK_GET(req->options, Z_Options_namedResultSets))
331 m_support_named_result_sets = true;
333 static const int masks[] = {
334 Z_Options_search, Z_Options_present,
335 Z_Options_namedResultSets, -1
337 for (i = 0; masks[i] != -1; i++)
338 if (ODR_MASK_GET(req->options, masks[i]))
339 ODR_MASK_SET(resp->options, masks[i]);
340 static const int versions[] = {
346 for (i = 0; versions[i] != -1; i++)
347 if (ODR_MASK_GET(req->protocolVersion, versions[i]))
348 ODR_MASK_SET(resp->protocolVersion, versions[i]);
351 *resp->preferredMessageSize = *req->preferredMessageSize;
352 *resp->maximumRecordSize = *req->maximumRecordSize;
354 else if (apdu_req->which == Z_APDU_close)
356 apdu_res = odr.create_close(apdu_req,
357 Z_Close_finished, 0);
358 package.session().close();
360 else if (apdu_req->which == Z_APDU_searchRequest)
362 Z_SearchRequest *req = apdu_req->u.searchRequest;
364 FrontendSets::iterator fset_it =
365 m_frontend_sets.find(req->resultSetName);
366 if (fset_it != m_frontend_sets.end())
368 // result set already exist
369 // if replace indicator is off: we return diagnostic if
370 // result set already exist.
371 if (*req->replaceIndicator == 0)
374 odr.create_searchResponse(
376 YAZ_BIB1_RESULT_SET_EXISTS_AND_REPLACE_INDICATOR_OFF,
378 package.response() = apdu_res;
380 m_frontend_sets.erase(fset_it);
382 if (req->query->which != Z_Query_type_1)
384 apdu_res = odr.create_searchResponse(
385 apdu_req, YAZ_BIB1_QUERY_TYPE_UNSUPP, 0);
387 else if (req->num_databaseNames != 1)
389 apdu_res = odr.create_searchResponse(
391 YAZ_BIB1_ACCESS_TO_SPECIFIED_DATABASE_DENIED, 0);
395 std::string db = req->databaseNames[0];
396 std::list<ConfPtr>::const_iterator it;
398 it = m_sparql->db_conf.begin();
399 for (; it != m_sparql->db_conf.end(); it++)
400 if (yaz_match_glob((*it)->db.c_str(), db.c_str()))
402 if (it == m_sparql->db_conf.end())
404 apdu_res = odr.create_searchResponse(
405 apdu_req, YAZ_BIB1_DATABASE_DOES_NOT_EXIST, db.c_str());
409 WRBUF addinfo_wr = wrbuf_alloc();
410 WRBUF sparql_wr = wrbuf_alloc();
412 yaz_sparql_from_rpn_wrbuf((*it)->s,
413 addinfo_wr, sparql_wr,
414 req->query->u.type_1);
417 apdu_res = odr.create_searchResponse(
419 wrbuf_len(addinfo_wr) ?
420 wrbuf_cstr(addinfo_wr) : 0);
424 apdu_res = run_sparql(package, apdu_req, odr,
425 wrbuf_cstr(sparql_wr),
428 wrbuf_destroy(addinfo_wr);
429 wrbuf_destroy(sparql_wr);
435 apdu_res = odr.create_close(apdu_req,
436 Z_Close_protocolError,
437 "sparql: unhandled APDU");
438 package.session().close();
442 package.response() = apdu_res;
445 void yf::SPARQL::process(mp::Package &package) const
448 SessionPtr p = get_session(package, &apdu);
451 p->handle_z(package, apdu);
455 release_session(package);
458 static mp::filter::Base* filter_creator()
460 return new mp::filter::SPARQL;
464 struct metaproxy_1_filter_struct metaproxy_1_filter_sparql = {
475 * c-file-style: "Stroustrup"
476 * indent-tabs-mode: nil
478 * vim: shiftwidth=4 tabstop=8 expandtab