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