ab8aa83d0ae36c57a43d7093825a5573924baf50
[libt2n] / src / client_wrapper.hxx
1 /*
2 Copyright (C) 2006 by Intra2net AG - Gerd v. Egidy
3
4 The software in this package is distributed under the GNU General
5 Public License version 2 (with a special exception described below).
6
7 A copy of GNU General Public License (GPL) is included in this distribution,
8 in the file COPYING.GPL.
9
10 As a special exception, if other files instantiate templates or use macros
11 or inline functions from this file, or you compile this file and link it
12 with other works to produce a work based on this file, this file
13 does not by itself cause the resulting work to be covered
14 by the GNU General Public License.
15
16 However the source code for this file must still be made available
17 in accordance with section (3) of the GNU General Public License.
18
19 This exception does not invalidate any other reasons why a work based
20 on this file might be covered by the GNU General Public License.
21 */
22 #ifndef __LIBT2N_CLIENT_WRAPPER
23 #define __LIBT2N_CLIENT_WRAPPER
24
25 #include <functional>
26
27 #include <boost/config.hpp>
28 #include <boost/bind.hpp>
29 #include <boost/function.hpp>
30 #include <boost/preprocessor.hpp>
31
32 #include <client.hxx>
33 #include <command_client.hxx>
34
35 #ifndef T2N_SINGLETON_WRAPPER_MAX_ARGS
36 #define T2N_SINGLETON_WRAPPER_MAX_ARGS 9
37 #endif
38
39 namespace libt2n
40 {
41
42 /** @brief provide access to client_connection singletons and call-handling function
43
44     This is an abstact base class for use with T2nSingletonWrapper. It provides access
45     to the singleton-client_connection and offers a handle-method which is used for
46     every call to a method on a t2n-server.
47 */
48 class ConnectionWrapper
49 {
50     private:
51         long long command_timeout_usec;
52         long long hello_timeout_usec;
53
54     protected:
55         log_level_values log_level;
56         std::ostream *logstream;
57         void set_logging_on_connection(client_connection& c);
58
59     public:
60         ConnectionWrapper()
61             : log_level(none), logstream(NULL), 
62               command_timeout_usec(command_client::command_timeout_usec_default),
63               hello_timeout_usec(command_client::hello_timeout_usec_default)
64             { }
65
66         virtual ~ConnectionWrapper()
67             { }
68
69         /** @brief return active connection, create new one if not existing
70
71             Return a pointer to an active client_connection. Use detail-data
72             stored within the derived class to create a new connection if needed,
73             otherwise return an alredy active connection. The derived class has
74             to take care of destroying the connection when not needed anymore.
75         */
76         virtual client_connection* get_connection()=0;
77
78         /** @brief this function is called on every execution of a method on a server
79
80             @param stubBase pointer to the command_client executing the call
81             @param f boost::function object containing the method to call and all parameters
82             @retval true if the call was successful and the original return-value of the called function can be used
83                     false if T2nSingletonWrapper has to create a return-value with the default-constructor
84
85             T2nSingletonWrapper will call this function on every execution of a server-side
86             method. This version will just call the function without any special treatment.
87             You can overload this function to implement different error handling strategies.
88         */
89         virtual bool handle(command_client* stubBase, boost::function< void() > f)
90         {
91             f();
92             return true;
93         }
94
95         long long get_command_timeout_usec(void)
96             { return command_timeout_usec; }
97
98         void set_command_timeout_usec(long long _command_timeout_usec)
99             { command_timeout_usec=_command_timeout_usec; }
100
101         long long get_hello_timeout_usec(void)
102             { return hello_timeout_usec; }
103
104         void set_hello_timeout_usec(long long _hello_timeout_usec)
105             { hello_timeout_usec=_hello_timeout_usec; }
106
107         virtual void set_logging(std::ostream *_logstream, log_level_values _log_level);
108
109         std::ostream* get_logstream(log_level_values level);
110 };
111
112 // contains the internal stuff needed for T2nWrapper
113 namespace detail
114 {
115
116     template< typename T >
117     struct TypeWrap
118     {
119         typedef T type;
120     };
121
122     template< >
123     struct TypeWrap< void >
124     {
125         typedef int type;
126     };
127
128     template< typename R >
129     struct Call
130     {
131         typedef boost::function< R() > FuncType;
132
133         FuncType function;
134         R& result;
135
136         Call( FuncType f, R& res ) : function(f), result( res ) {}
137
138         void operator()()
139         {
140             result= function();
141         }
142     };
143
144     template< >
145     struct Call<void>
146     {
147         typedef boost::function< void() > FuncType;
148         typedef TypeWrap< void >::type ResultType;
149
150         FuncType function;
151         ResultType& result;
152
153         Call( FuncType f, ResultType& res ) : function(f), result( res ) {}
154
155         void operator()()
156         {
157             function();
158             result= ResultType();
159         }
160     };
161 } // eo namespace detail
162
163 class T2nSingletonWrapperMessages
164 {
165     protected:
166         static const char* NotInitializedMessage;
167 };
168
169 /** @brief wrap calls to server-side-functions with different error-handling strategies
170
171     Template class to access a process-wide singleton server-connection and to wrap all
172     calls using this connection with an error-handling strategy (e.g. to reconnect when
173     the connection broke). The source looks very complicated due to heavy use of templates,
174     look at the 3rd codegen example to see how to use it.
175
176     @par Example
177     Calling remote methods is usually done via t2n_exec, this saves you from always
178     specifying which T2nSingletonWrapper-template to use when calling T2nSingletonWrapper::exec
179     @code
180     t2n_exec(&cmd_group_t2nexample_client::testfunc)("the answer is %d",42)
181     @endcode
182 */
183 template< class Client >
184 class T2nSingletonWrapper : public T2nSingletonWrapperMessages
185 {
186     private:
187         std::auto_ptr<Client> Stub;
188
189         static std::auto_ptr<T2nSingletonWrapper> SingletonObject;
190         static std::auto_ptr<ConnectionWrapper> WrappedConnection;
191
192         /// @cond
193         // create a prep-method for each possible number of parameters
194 #define _GEN_ARG(z,n,d) Arg ## n arg ##n
195 #define _GEN_PREP(z,n,d) \
196         template< typename R  BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_PARAMS(n,typename Arg) > \
197         static boost::function< R(Client*) > prep \
198         ( \
199             R(Client::*f)( BOOST_PP_ENUM_PARAMS(n,Arg) ) \
200             BOOST_PP_COMMA_IF(n) \
201             BOOST_PP_ENUM( n, _GEN_ARG, ~ ) \
202         ) \
203         { \
204             return boost::bind< R > \
205                 ( \
206                     f, _1  BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_PARAMS(n,arg) \
207                 ); \
208         } // eo prep
209
210         BOOST_PP_REPEAT( BOOST_PP_ADD(T2N_SINGLETON_WRAPPER_MAX_ARGS,1) , _GEN_PREP, ~ )
211
212 #undef _GEN_PREP
213 #undef _GEN_ARG
214         /// @endcond
215
216         T2nSingletonWrapper(std::auto_ptr<Client> stub)
217         {
218             Stub=stub;
219         }
220
221         static void init()
222         {
223             if (WrappedConnection.get() == NULL)
224                 throw std::logic_error(NotInitializedMessage);
225
226             std::auto_ptr<Client> stub(new Client(WrappedConnection->get_connection(),
227                 WrappedConnection->get_command_timeout_usec(),
228                 WrappedConnection->get_hello_timeout_usec()));
229
230             SingletonObject=std::auto_ptr<T2nSingletonWrapper>(new T2nSingletonWrapper(stub));
231         }
232
233         template< typename R >
234         static
235         typename detail::TypeWrap<R>::type real_exec( boost::function< R(Client*) > f)
236         {
237             ensure_singleton_there();
238
239             typename detail::TypeWrap<R>::type result;
240
241             // bind our Client-object and the local result
242             detail::Call<R> call( boost::bind( f, SingletonObject->Stub.get()), result );
243
244             // let the wrapper-handler call the fully-bound function
245             if (!WrappedConnection->handle(SingletonObject->Stub.get(),call))
246             {
247                 // create an result with default-constructor if the handler could not
248                 // successfully do a call but no exception occured
249                 result=typename detail::TypeWrap<R>::type();
250             }
251             return result;
252         }
253
254     public:
255
256         /** @brief tell the wrapper which connection to use
257             @param wrappedConnection the connection to establish when needed
258         */
259         static void set_connection(std::auto_ptr<ConnectionWrapper> wrappedConnection)
260         {
261             WrappedConnection=wrappedConnection;
262
263             // reset the singleton to NULL because the singleton must be constructed with current WrappedConnection
264             if (SingletonObject.get() != NULL)
265                 SingletonObject.reset();
266         }
267
268         /// return a pointer to the ConnectionWrapper currently in use
269         static ConnectionWrapper* get_connection_wrapper(void)
270             { return WrappedConnection.get(); }
271
272         /// manually establish the connection without actually executing a call
273         static void ensure_singleton_there(void)
274         {
275             if (SingletonObject.get() == NULL)
276                 init();
277         }
278
279         /// @cond
280         // create an exec-method for each possible number of parameters
281 #define _GEN_PLACEHOLDER(z,n,d) BOOST_PP_CAT(_,BOOST_PP_ADD(n,1))
282 #define _GEN_EXEC(z,n,d) \
283         template< typename R  BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_PARAMS(n,typename Arg) > \
284         static boost::function< R( BOOST_PP_ENUM_PARAMS(n,Arg) ) > exec \
285         ( \
286             R(Client::*f)( BOOST_PP_ENUM_PARAMS(n,Arg) ) \
287         ) \
288         { \
289             boost::function<R(Client*)>(*p)(R(Client::*)( BOOST_PP_ENUM_PARAMS(n,Arg) ) \
290                     BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_PARAMS(n,Arg)) \
291                 = &T2nSingletonWrapper::template prep<R BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_PARAMS(n,Arg) >; \
292             return boost::bind \
293             ( \
294                 T2nSingletonWrapper::template real_exec<R>, \
295                 boost::bind( p, f BOOST_PP_COMMA_IF(n) \
296                 BOOST_PP_ENUM(n, _GEN_PLACEHOLDER, ~ ) ) \
297             ); \
298         } // eo exec
299
300         BOOST_PP_REPEAT( BOOST_PP_ADD(T2N_SINGLETON_WRAPPER_MAX_ARGS,1), _GEN_EXEC, ~ )
301
302 #undef _GEN_EXEC
303 #undef _GEN_PLACEHOLDER
304         /// @endcond
305
306 };
307
308 /// @cond
309 // create an t2n_exec-method for each possible number of parameters
310 #define _GEN_EXEC(z,n,d) \
311         template< class Client, typename R  BOOST_PP_COMMA_IF(n) BOOST_PP_ENUM_PARAMS(n,typename Arg) > \
312         static boost::function< R( BOOST_PP_ENUM_PARAMS(n,Arg) ) > t2n_exec \
313         ( \
314             R(Client::*f)( BOOST_PP_ENUM_PARAMS(n,Arg) ) \
315         ) \
316         { \
317             return T2nSingletonWrapper<Client>::exec(f); \
318         } // eo exec
319
320         BOOST_PP_REPEAT( BOOST_PP_ADD(T2N_SINGLETON_WRAPPER_MAX_ARGS,1), _GEN_EXEC, ~ )
321
322 #undef _GEN_EXEC
323 #undef _GEN_PLACEHOLDER
324 /// @endcond
325
326 }
327 #endif