Updated copyright headers. Omit CVS ID.
[metaproxy-moved-to-github.git] / src / filter_z3950_client.cpp
1 /* This file is part of Metaproxy.
2    Copyright (C) 2005-2008 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 /* $Id: filter_z3950_client.cpp,v 1.32 2008-02-20 15:07:52 adam Exp $
19    Copyright (c) 2005-2007, Index Data.
20
21    See the LICENSE file for details
22  */
23
24 #include "config.hpp"
25
26 #include "filter.hpp"
27 #include "package.hpp"
28 #include "util.hpp"
29 #include "filter_z3950_client.hpp"
30
31 #include <map>
32 #include <stdexcept>
33 #include <list>
34 #include <iostream>
35
36 #include <boost/thread/mutex.hpp>
37 #include <boost/thread/condition.hpp>
38
39 #include <yaz/zgdu.h>
40 #include <yaz/log.h>
41 #include <yaz/otherinfo.h>
42 #include <yaz/diagbib1.h>
43
44 #include <yazpp/socket-manager.h>
45 #include <yazpp/pdu-assoc.h>
46 #include <yazpp/z-assoc.h>
47
48 namespace mp = metaproxy_1;
49 namespace yf = mp::filter;
50
51 namespace metaproxy_1 {
52     namespace filter {
53         class Z3950Client::Assoc : public yazpp_1::Z_Assoc{
54             friend class Rep;
55             Assoc(yazpp_1::SocketManager *socket_manager,
56                   yazpp_1::IPDU_Observable *PDU_Observable,
57                   std::string host, int timeout);
58             ~Assoc();
59             void connectNotify();
60             void failNotify();
61             void timeoutNotify();
62             void recv_GDU(Z_GDU *gdu, int len);
63             yazpp_1::IPDU_Observer* sessionNotify(
64                 yazpp_1::IPDU_Observable *the_PDU_Observable,
65                 int fd);
66
67             yazpp_1::SocketManager *m_socket_manager;
68             yazpp_1::IPDU_Observable *m_PDU_Observable;
69             Package *m_package;
70             bool m_in_use;
71             bool m_waiting;
72             bool m_destroyed;
73             bool m_connected;
74             int m_queue_len;
75             int m_time_elapsed;
76             int m_time_max;
77             std::string m_host;
78         };
79
80         class Z3950Client::Rep {
81         public:
82             // number of seconds to wait before we give up request
83             int m_timeout_sec;
84             std::string m_default_target;
85             std::string m_force_target;
86             boost::mutex m_mutex;
87             boost::condition m_cond_session_ready;
88             std::map<mp::Session,Z3950Client::Assoc *> m_clients;
89             Z3950Client::Assoc *get_assoc(Package &package);
90             void send_and_receive(Package &package,
91                                   yf::Z3950Client::Assoc *c);
92             void release_assoc(Package &package);
93         };
94     }
95 }
96
97 using namespace mp;
98
99 yf::Z3950Client::Assoc::Assoc(yazpp_1::SocketManager *socket_manager,
100                               yazpp_1::IPDU_Observable *PDU_Observable,
101                               std::string host, int timeout_sec)
102     :  Z_Assoc(PDU_Observable),
103        m_socket_manager(socket_manager), m_PDU_Observable(PDU_Observable),
104        m_package(0), m_in_use(true), m_waiting(false), 
105        m_destroyed(false), m_connected(false), m_queue_len(1),
106        m_time_elapsed(0), m_time_max(timeout_sec), 
107        m_host(host)
108 {
109     // std::cout << "create assoc " << this << "\n";
110 }
111
112 yf::Z3950Client::Assoc::~Assoc()
113 {
114     // std::cout << "destroy assoc " << this << "\n";
115 }
116
117 void yf::Z3950Client::Assoc::connectNotify()
118 {
119     m_waiting = false;
120
121     m_connected = true;
122 }
123
124 void yf::Z3950Client::Assoc::failNotify()
125 {
126     m_waiting = false;
127
128     mp::odr odr;
129
130     if (m_package)
131     {
132         Z_GDU *gdu = m_package->request().get();
133         Z_APDU *apdu = 0;
134         if (gdu && gdu->which == Z_GDU_Z3950)
135             apdu = gdu->u.z3950;
136         
137         m_package->response() = odr.create_close(apdu, Z_Close_peerAbort, 0);
138         m_package->session().close();
139     }
140 }
141
142 void yf::Z3950Client::Assoc::timeoutNotify()
143 {
144     m_time_elapsed++;
145     if (m_time_elapsed >= m_time_max)
146     {
147         m_waiting = false;
148
149         mp::odr odr;
150         
151         if (m_package)
152         {
153             Z_GDU *gdu = m_package->request().get();
154             Z_APDU *apdu = 0;
155             if (gdu && gdu->which == Z_GDU_Z3950)
156                 apdu = gdu->u.z3950;
157         
158             if (m_connected)
159                 m_package->response() =
160                     odr.create_close(apdu, Z_Close_lackOfActivity, 0);
161             else
162                 m_package->response() = 
163                     odr.create_close(apdu, Z_Close_peerAbort, 0);
164                 
165             m_package->session().close();
166         }
167     }
168 }
169
170 void yf::Z3950Client::Assoc::recv_GDU(Z_GDU *gdu, int len)
171 {
172     m_waiting = false;
173
174     if (m_package)
175         m_package->response() = gdu;
176 }
177
178 yazpp_1::IPDU_Observer *yf::Z3950Client::Assoc::sessionNotify(
179     yazpp_1::IPDU_Observable *the_PDU_Observable,
180     int fd)
181 {
182     return 0;
183 }
184
185
186 yf::Z3950Client::Z3950Client() :  m_p(new yf::Z3950Client::Rep)
187 {
188     m_p->m_timeout_sec = 30;
189 }
190
191 yf::Z3950Client::~Z3950Client() {
192 }
193
194 yf::Z3950Client::Assoc *yf::Z3950Client::Rep::get_assoc(Package &package) 
195 {
196     // only one thread messes with the clients list at a time
197     boost::mutex::scoped_lock lock(m_mutex);
198
199     std::map<mp::Session,yf::Z3950Client::Assoc *>::iterator it;
200     
201     Z_GDU *gdu = package.request().get();
202     // only deal with Z39.50
203     if (!gdu || gdu->which != Z_GDU_Z3950)
204     {
205         package.move();
206         return 0;
207     }
208     it = m_clients.find(package.session());
209     if (it != m_clients.end())
210     {
211         it->second->m_queue_len++;
212         while(true)
213         {
214 #if 0
215             // double init .. NOT working yet
216             if (gdu && gdu->which == Z_GDU_Z3950 &&
217                 gdu->u.z3950->which == Z_APDU_initRequest)
218             {
219                 yazpp_1::SocketManager *s = it->second->m_socket_manager;
220                 delete it->second;  // destroy Z_Assoc
221                 delete s;    // then manager
222                 m_clients.erase(it);
223                 break;
224             }
225 #endif
226             if (!it->second->m_in_use)
227             {
228                 it->second->m_in_use = true;
229                 return it->second;
230             }
231             m_cond_session_ready.wait(lock);
232         }
233     }
234     // new Z39.50 session ..
235     Z_APDU *apdu = gdu->u.z3950;
236     // check that it is init. If not, close
237     if (apdu->which != Z_APDU_initRequest)
238     {
239         mp::odr odr;
240         
241         package.response() = odr.create_close(apdu,
242                                               Z_Close_protocolError,
243                                               "First PDU was not an "
244                                               "Initialize Request");
245         package.session().close();
246         return 0;
247     }
248     std::string target = m_force_target;
249     if (!target.length())
250     {
251         target = m_default_target;
252         std::list<std::string> vhosts;
253         mp::util::remove_vhost_otherinfo(&apdu->u.initRequest->otherInfo,
254                                          vhosts);
255         size_t no_vhosts = vhosts.size();
256         if (no_vhosts == 1)
257         {
258             std::list<std::string>::const_iterator v_it = vhosts.begin();
259             target = *v_it;
260         }
261         else if (no_vhosts == 0)
262         {
263             if (!target.length())
264             {
265                 // no default target. So we don't know where to connect
266                 mp::odr odr;
267                 package.response() = odr.create_initResponse(
268                     apdu,
269                     YAZ_BIB1_INIT_NEGOTIATION_OPTION_REQUIRED,
270                     "z3950_client: No virtal host given");
271                 
272                 package.session().close();
273                 return 0;
274             }
275         }
276         else if (no_vhosts > 1)
277         {
278             mp::odr odr;
279             package.response() = odr.create_initResponse(
280                 apdu,
281                 YAZ_BIB1_COMBI_OF_SPECIFIED_DATABASES_UNSUPP,
282                 "z3950_client: Can not cope with multiple vhosts");
283             package.session().close();
284             return 0;
285         }
286     }
287     std::list<std::string> dblist;
288     std::string host;
289     mp::util::split_zurl(target, host, dblist);
290     
291     if (dblist.size())
292     {
293         ; // z3950_client: Databases in vhost ignored
294     }
295
296     yazpp_1::SocketManager *sm = new yazpp_1::SocketManager;
297     yazpp_1::PDU_Assoc *pdu_as = new yazpp_1::PDU_Assoc(sm);
298     yf::Z3950Client::Assoc *as = new yf::Z3950Client::Assoc(sm, pdu_as,
299                                                             host.c_str(),
300                                                             m_timeout_sec);
301     m_clients[package.session()] = as;
302     return as;
303 }
304
305 void yf::Z3950Client::Rep::send_and_receive(Package &package,
306                                             yf::Z3950Client::Assoc *c)
307 {
308     Z_GDU *gdu = package.request().get();
309
310     if (c->m_destroyed)
311         return;
312
313     if (!gdu || gdu->which != Z_GDU_Z3950)
314         return;
315
316     c->m_time_elapsed = 0;
317     c->m_package = &package;
318     c->m_waiting = true;
319     if (!c->m_connected)
320     {
321         c->client(c->m_host.c_str());
322         c->timeout(1);  // so timeoutNotify gets called once per second
323
324         while (!c->m_destroyed && c->m_waiting 
325                && c->m_socket_manager->processEvent() > 0)
326             ;
327     }
328     if (!c->m_connected)
329     {
330         return;
331     }
332
333     // prepare response
334     c->m_time_elapsed = 0;
335     c->m_waiting = true;
336     
337     // relay the package  ..
338     int len;
339     c->send_GDU(gdu, &len);
340
341     switch(gdu->u.z3950->which)
342     {
343     case Z_APDU_triggerResourceControlRequest:
344         // request only..
345         break;
346     default:
347         // for the rest: wait for a response PDU
348         while (!c->m_destroyed && c->m_waiting
349                && c->m_socket_manager->processEvent() > 0)
350             ;
351         break;
352     }
353 }
354
355 void yf::Z3950Client::Rep::release_assoc(Package &package)
356 {
357     boost::mutex::scoped_lock lock(m_mutex);
358     std::map<mp::Session,yf::Z3950Client::Assoc *>::iterator it;
359     
360     it = m_clients.find(package.session());
361     if (it != m_clients.end())
362     {
363         Z_GDU *gdu = package.request().get();
364         if (gdu && gdu->which == Z_GDU_Z3950)
365         {   // only Z39.50 packages lock in get_assoc.. release it
366             it->second->m_in_use = false;
367             it->second->m_queue_len--;
368         }
369
370         if (package.session().is_closed())
371         {
372             // destroy hint (send_and_receive)
373             it->second->m_destroyed = true;
374             
375             // wait until no one is waiting for it.
376             while (it->second->m_queue_len)
377                 m_cond_session_ready.wait(lock);
378             
379             // the Z_Assoc and PDU_Assoc must be destroyed before
380             // the socket manager.. so pull that out.. first..
381             yazpp_1::SocketManager *s = it->second->m_socket_manager;
382             delete it->second;  // destroy Z_Assoc
383             delete s;    // then manager
384             m_clients.erase(it);
385         }
386         m_cond_session_ready.notify_all();
387     }
388 }
389
390 void yf::Z3950Client::process(Package &package) const
391 {
392     yf::Z3950Client::Assoc *c = m_p->get_assoc(package);
393     if (c)
394     {
395         m_p->send_and_receive(package, c);
396     }
397     m_p->release_assoc(package);
398 }
399
400 void yf::Z3950Client::configure(const xmlNode *ptr, bool test_only)
401 {
402     for (ptr = ptr->children; ptr; ptr = ptr->next)
403     {
404         if (ptr->type != XML_ELEMENT_NODE)
405             continue;
406         if (!strcmp((const char *) ptr->name, "timeout"))
407         {
408             m_p->m_timeout_sec = mp::xml::get_int(ptr->children, 30);
409         }
410         else if (!strcmp((const char *) ptr->name, "default_target"))
411         {
412             m_p->m_default_target = mp::xml::get_text(ptr);
413         }
414         else if (!strcmp((const char *) ptr->name, "force_target"))
415         {
416             m_p->m_force_target = mp::xml::get_text(ptr);
417         }
418         else
419         {
420             throw mp::filter::FilterException("Bad element " 
421                                                + std::string((const char *)
422                                                              ptr->name));
423         }
424     }
425 }
426
427 static mp::filter::Base* filter_creator()
428 {
429     return new mp::filter::Z3950Client;
430 }
431
432 extern "C" {
433     struct metaproxy_1_filter_struct metaproxy_1_filter_z3950_client = {
434         0,
435         "z3950_client",
436         filter_creator
437     };
438 }
439
440 /*
441  * Local variables:
442  * c-basic-offset: 4
443  * indent-tabs-mode: nil
444  * c-file-style: "stroustrup"
445  * End:
446  * vim: shiftwidth=4 tabstop=8 expandtab
447  */