Moving SPARQL to separate Git repo
[mp-sparql-moved-to-github.git] / src / filter_sparql.cpp
1 /* This file is part of Metaproxy.
2    Copyright (C) Index Data
3
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
7 version.
8
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
12 for more details.
13
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
17 */
18
19 #include <metaproxy/package.hpp>
20 #include <metaproxy/util.hpp>
21 #include <yaz/log.h>
22 #include <yaz/srw.h>
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>
28 #include "sparql.h"
29
30 #include <yaz/zgdu.h>
31
32 namespace mp = metaproxy_1;
33 namespace yf = mp::filter;
34
35 namespace metaproxy_1 {
36     namespace filter {
37         class SPARQL : public Base {
38             class Session;
39             class Rep;
40             class Conf;
41
42             typedef boost::shared_ptr<Session> SessionPtr;
43             typedef boost::shared_ptr<Conf> ConfPtr;
44         public:
45             SPARQL();
46             ~SPARQL();
47             void process(metaproxy_1::Package & package) const;
48             void configure(const xmlNode * ptr, bool test_only,
49                            const char *path);
50             SessionPtr get_session(Package &package, Z_APDU **apdu) const;
51             void release_session(Package &package) const;
52             boost::scoped_ptr<Rep> m_p;
53             std::list<ConfPtr> db_conf;
54         };
55         class SPARQL::Conf {
56         public:
57             std::string db;
58             std::string uri;
59             yaz_sparql_t s;
60             ~Conf();
61         };
62         class SPARQL::Rep {
63             friend class SPARQL;
64             boost::condition m_cond_session_ready;
65             boost::mutex m_mutex;
66             std::map<mp::Session,SessionPtr> m_clients;
67         };
68         class SPARQL::Session {
69         public:
70             Session(const SPARQL *);
71             ~Session();
72             void handle_z(Package &package, Z_APDU *apdu);
73             Z_APDU *run_sparql(mp::Package &package,
74                                Z_APDU *apdu_req,
75                                mp::odr &odr,
76                                const char *sparql_query,
77                                const char *uri);
78             bool m_in_use;
79         private:
80             bool m_support_named_result_sets;
81             const SPARQL *m_sparql;
82         };
83     }
84 }
85
86 yf::SPARQL::SPARQL() : m_p(new Rep)
87 {
88 }
89
90 yf::SPARQL::~SPARQL()
91 {
92 }
93
94 void yf::SPARQL::configure(const xmlNode *xmlnode, bool test_only,
95                            const char *path)
96 {
97     const xmlNode *ptr = xmlnode->children;
98
99     for (; ptr; ptr = ptr->next)
100     {
101         if (ptr->type != XML_ELEMENT_NODE)
102             continue;
103         if (!strcmp((const char *) ptr->name, "db"))
104         {
105             yaz_sparql_t s = yaz_sparql_create();
106             ConfPtr conf(new Conf);
107             conf->s = s;
108
109             const struct _xmlAttr *attr;
110             for (attr = ptr->properties; attr; attr = attr->next)
111             {
112                 if (!strcmp((const char *) attr->name, "path"))
113                     conf->db = mp::xml::get_text(attr->children);
114                 else if (!strcmp((const char *) attr->name, "uri"))
115                     conf->uri = mp::xml::get_text(attr->children);
116                 else
117                     throw mp::filter::FilterException(
118                         "Bad attribute " + std::string((const char *)
119                                                        attr->name));
120             }
121             xmlNode *p = ptr->children;
122             for (; p; p = p->next)
123             {
124                 if (p->type != XML_ELEMENT_NODE)
125                     continue;
126                 std::string name = (const char *) p->name;
127                 const struct _xmlAttr *attr;
128                 for (attr = p->properties; attr; attr = attr->next)
129                 {
130                     if (!strcmp((const char *) attr->name, "type"))
131                     {
132                         name.append(".");
133                         name.append(mp::xml::get_text(attr->children));
134                     }
135                     else
136                         throw mp::filter::FilterException(
137                             "Bad attribute " + std::string((const char *)
138                                                            attr->name));
139                 }
140                 std::string value = mp::xml::get_text(p);
141                 if (yaz_sparql_add_pattern(s, name.c_str(), value.c_str()))
142                 {
143                     throw mp::filter::FilterException(
144                         "Bad SPARQL config " + name);
145                 }
146             }
147             if (!conf->uri.length())
148             {
149                 throw mp::filter::FilterException("Missing uri");
150             }
151             if (!conf->db.length())
152             {
153                 throw mp::filter::FilterException("Missing path");
154             }
155             db_conf.push_back(conf);
156         }
157         else
158         {
159             throw mp::filter::FilterException
160                 ("Bad element "
161                  + std::string((const char *) ptr->name)
162                  + " in sparql filter");
163         }
164     }
165 }
166
167 yf::SPARQL::Conf::~Conf()
168 {
169     yaz_sparql_destroy(s);
170 }
171
172 yf::SPARQL::Session::Session(const SPARQL *sparql) :
173     m_in_use(true),
174     m_support_named_result_sets(false),
175     m_sparql(sparql)
176 {
177 }
178
179 yf::SPARQL::Session::~Session()
180 {
181 }
182
183 yf::SPARQL::SessionPtr yf::SPARQL::get_session(Package & package,
184                                                Z_APDU **apdu) const
185 {
186     SessionPtr ptr0;
187
188     Z_GDU *gdu = package.request().get();
189
190     boost::mutex::scoped_lock lock(m_p->m_mutex);
191
192     std::map<mp::Session,SPARQL::SessionPtr>::iterator it;
193
194     if (gdu && gdu->which == Z_GDU_Z3950)
195         *apdu = gdu->u.z3950;
196     else
197         *apdu = 0;
198
199     while (true)
200     {
201         it = m_p->m_clients.find(package.session());
202         if (it == m_p->m_clients.end())
203             break;
204         if (!it->second->m_in_use)
205         {
206             it->second->m_in_use = true;
207             return it->second;
208         }
209         m_p->m_cond_session_ready.wait(lock);
210     }
211     if (!*apdu)
212         return ptr0;
213
214     // new Z39.50 session ..
215     SessionPtr p(new Session(this));
216     m_p->m_clients[package.session()] = p;
217     return p;
218 }
219
220 void yf::SPARQL::release_session(Package &package) const
221 {
222     boost::mutex::scoped_lock lock(m_p->m_mutex);
223     std::map<mp::Session,SessionPtr>::iterator it;
224
225     it = m_p->m_clients.find(package.session());
226     if (it != m_p->m_clients.end())
227     {
228         it->second->m_in_use = false;
229
230         if (package.session().is_closed())
231             m_p->m_clients.erase(it);
232         m_p->m_cond_session_ready.notify_all();
233     }
234 }
235
236 Z_APDU *yf::SPARQL::Session::run_sparql(mp::Package &package,
237                                         Z_APDU *apdu_req,
238                                         mp::odr &odr,
239                                         const char *sparql_query,
240                                         const char *uri)
241 {
242     Package http_package(package.session(), package.origin());
243
244     http_package.copy_filter(package);
245     Z_GDU *gdu = z_get_HTTP_Request_uri(odr, uri, 0, 1);
246
247     z_HTTP_header_add(odr, &gdu->u.HTTP_Request->headers,
248                       "Content-Type", "application/x-www-form-urlencoded");
249     const char *names[2];
250     names[0] = "query";
251     names[1] = 0;
252     const char *values[1];
253     values[0] = sparql_query;
254     char *path = 0;
255     yaz_array_to_uri(&path, odr, (char **) names, (char **) values);
256
257     gdu->u.HTTP_Request->content_buf = path;
258     gdu->u.HTTP_Request->content_len = strlen(path);
259
260
261     yaz_log(YLOG_LOG, "sparql: HTTP request\n%s", sparql_query);
262
263     http_package.request() = gdu;
264     http_package.move();
265
266     Z_GDU *gdu_resp = http_package.response().get();
267     if (gdu_resp && gdu_resp->which == Z_GDU_HTTP_Response)
268     {
269         Z_HTTP_Response *resp = gdu_resp->u.HTTP_Response;
270     }
271     else
272     {
273         yaz_log(YLOG_LOG, "sparql: no HTTP response");
274     }
275     Z_APDU *apdu_res = odr.create_searchResponse(apdu_req, 0, 0);
276     return apdu_res;
277 }
278
279 void yf::SPARQL::Session::handle_z(mp::Package &package, Z_APDU *apdu_req)
280 {
281     mp::odr odr;
282     Z_APDU *apdu_res = 0;
283     if (apdu_req->which == Z_APDU_initRequest)
284     {
285         apdu_res = odr.create_initResponse(apdu_req, 0, 0);
286         Z_InitRequest *req = apdu_req->u.initRequest;
287         Z_InitResponse *resp = apdu_res->u.initResponse;
288
289         resp->implementationName = odr_strdup(odr, "sparql");
290         if (ODR_MASK_GET(req->options, Z_Options_namedResultSets))
291             m_support_named_result_sets = true;
292         int i;
293         static const int masks[] = {
294             Z_Options_search, Z_Options_present,
295             Z_Options_namedResultSets, -1
296         };
297         for (i = 0; masks[i] != -1; i++)
298             if (ODR_MASK_GET(req->options, masks[i]))
299                 ODR_MASK_SET(resp->options, masks[i]);
300         static const int versions[] = {
301             Z_ProtocolVersion_1,
302             Z_ProtocolVersion_2,
303             Z_ProtocolVersion_3,
304             -1
305         };
306         for (i = 0; versions[i] != -1; i++)
307             if (ODR_MASK_GET(req->protocolVersion, versions[i]))
308                 ODR_MASK_SET(resp->protocolVersion, versions[i]);
309             else
310                 break;
311         *resp->preferredMessageSize = *req->preferredMessageSize;
312         *resp->maximumRecordSize = *req->maximumRecordSize;
313     }
314     else if (apdu_req->which == Z_APDU_close)
315     {
316         apdu_res = odr.create_close(apdu_req,
317                                     Z_Close_finished, 0);
318         package.session().close();
319     }
320     else if (apdu_req->which == Z_APDU_searchRequest)
321     {
322         Z_SearchRequest *req = apdu_req->u.searchRequest;
323
324         if (req->query->which != Z_Query_type_1)
325         {
326             apdu_res = odr.create_searchResponse(
327                 apdu_req, YAZ_BIB1_QUERY_TYPE_UNSUPP, 0);
328         }
329         else if (req->num_databaseNames != 1)
330         {
331             apdu_res = odr.create_searchResponse(
332                 apdu_req,
333                 YAZ_BIB1_ACCESS_TO_SPECIFIED_DATABASE_DENIED, 0);
334         }
335         else
336         {
337             std::string db = req->databaseNames[0];
338             std::list<ConfPtr>::const_iterator it;
339
340             it = m_sparql->db_conf.begin();
341             for (; it != m_sparql->db_conf.end(); it++)
342                 if (yaz_match_glob((*it)->db.c_str(), db.c_str()))
343                     break;
344             if (it == m_sparql->db_conf.end())
345             {
346                 apdu_res = odr.create_searchResponse(
347                     apdu_req, YAZ_BIB1_DATABASE_DOES_NOT_EXIST, db.c_str());
348             }
349             else
350             {
351                 WRBUF addinfo_wr = wrbuf_alloc();
352                 WRBUF sparql_wr = wrbuf_alloc();
353                 int error =
354                     yaz_sparql_from_rpn_wrbuf((*it)->s,
355                                               addinfo_wr, sparql_wr,
356                                               req->query->u.type_1);
357                 if (error)
358                 {
359                     apdu_res = odr.create_searchResponse(
360                         apdu_req, error,
361                         wrbuf_len(addinfo_wr) ?
362                         wrbuf_cstr(addinfo_wr) : 0);
363                 }
364                 else
365                 {
366                     apdu_res = run_sparql(package, apdu_req, odr,
367                                           wrbuf_cstr(sparql_wr),
368                                           (*it)->uri.c_str());
369                 }
370                 wrbuf_destroy(addinfo_wr);
371                 wrbuf_destroy(sparql_wr);
372             }
373         }
374     }
375     else
376     {
377         apdu_res = odr.create_close(apdu_req,
378                                     Z_Close_protocolError,
379                                     "sparql: unhandled APDU");
380         package.session().close();
381     }
382
383     assert(apdu_res);
384     package.response() = apdu_res;
385 }
386
387 void yf::SPARQL::process(mp::Package &package) const
388 {
389     Z_APDU *apdu;
390     SessionPtr p = get_session(package, &apdu);
391     if (p && apdu)
392     {
393         p->handle_z(package, apdu);
394     }
395     else
396         package.move();
397     release_session(package);
398 }
399
400 static mp::filter::Base* filter_creator()
401 {
402     return new mp::filter::SPARQL;
403 }
404
405 extern "C" {
406     struct metaproxy_1_filter_struct metaproxy_1_filter_sparql = {
407         0,
408         "sparql",
409         filter_creator
410     };
411 }
412
413
414 /*
415  * Local variables:
416  * c-basic-offset: 4
417  * c-file-style: "Stroustrup"
418  * indent-tabs-mode: nil
419  * End:
420  * vim: shiftwidth=4 tabstop=8 expandtab
421  */
422