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