]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/perlmods/OpenSRF/Application.pm
registering classes during load and introspection
[OpenSRF.git] / src / perlmods / OpenSRF / Application.pm
1 package OpenSRF::Application;
2 use vars qw/$_app $log @_METHODS $thunk $server_class/;
3
4 use base qw/OpenSRF/;
5 use OpenSRF::AppSession;
6 use OpenSRF::DomainObject::oilsMethod;
7 use OpenSRF::DomainObject::oilsResponse qw/:status/;
8 use OpenSRF::Utils::Logger qw/:level/;
9 use Data::Dumper;
10 use Time::HiRes qw/time/;
11 use OpenSRF::EX qw/:try/;
12 use Carp;
13 #use OpenSRF::UnixServer;  # to get the server class from UnixServer::App
14
15 sub DESTROY{};
16
17 use strict;
18 use warnings;
19
20 $log = 'OpenSRF::Utils::Logger';
21
22 our $in_request = 0;
23 our @pending_requests;
24
25 sub api_name {
26         my $self = shift;
27         return $self->{api_name};
28 }
29
30 sub server_class {
31         my $class = shift;
32         if($class) {
33                 $server_class = $class;
34         }
35         return $server_class;
36 }
37
38 sub thunk {
39         my $self = shift;
40         my $flag = shift;
41         $thunk = $flag if (defined $flag);
42         return $thunk;
43 }
44
45 sub application_implementation {
46         my $self = shift;
47         my $app = shift;
48
49         if (defined $app) {
50                 $_app = $app;
51                 eval "use $_app;";
52                 if( $@ ) {
53                         $log->error( "Error loading application_implementation: $app -> $@", ERROR);
54                 }
55
56         }
57
58         return $_app;
59 }
60
61 sub handler {
62         my ($self, $session, $app_msg) = @_;
63
64         if( ! $app_msg ) {
65                 return 0;  # error?
66         }
67
68         $log->debug( "In Application::handler()", DEBUG );
69
70         my $app = $self->application_implementation;
71
72         if( $app ) {
73                 $log->debug( "Application is $app", DEBUG);
74         }
75         $log->debug( "Message is ".$app_msg->toString(1), INTERNAL);
76
77
78         if ($session->last_message_type eq 'REQUEST') {
79                 $log->debug( "We got a REQUEST: ". $app_msg->method, INFO );
80
81                 my $method_name = $app_msg->method;
82                 $log->debug( " * Looking up $method_name inside $app", DEBUG);
83
84                 my $method_proto = $session->last_message_api_level;
85                 $log->debug( " * Method API Level [$method_proto]", DEBUG);
86
87                 my $coderef = $app->method_lookup( $method_name, $method_proto, 1, 1 );
88
89                 unless ($coderef) {
90                         $session->status( OpenSRF::DomainObject::oilsMethodException->new() );
91                         return 1;
92                 }
93
94                 $log->debug( " (we got coderef $coderef", DEBUG);
95
96                 unless ($session->continue_request) {
97                         $session->status(
98                                 OpenSRF::DomainObject::oilsConnectStatus->new(
99                                                 statusCode => STATUS_REDIRECTED(),
100                                                 status => 'Disconnect on max requests' ) );
101                         $session->kill_me;
102                         return 1;
103                 }
104
105                 if (ref $coderef) {
106                         my @args = $app_msg->params;
107                         my $appreq = OpenSRF::AppRequest->new( $session );
108
109                         $log->debug( "in_request = $in_request : [" . $appreq->threadTrace."]", DEBUG );
110                         if( $in_request ) {
111                                 $log->debug( "Pushing onto pending requests: " . $appreq->threadTrace, DEBUG );
112                                 push @pending_requests, [ $appreq, \@args, $coderef ]; 
113                                 return 1;
114                         }
115
116
117                         $in_request++;
118
119                         $log->debug( "Executing coderef for {$method_name -> ".join(', ', @args)."}", INTERNAL );
120
121                         my $resp;
122                         try {
123                                 my $start = time();
124                                 $resp = $coderef->run( $appreq, @args); 
125                                 my $time = sprintf '%.3f', time() - $start;
126                                 $log->debug( "Method duration for {$method_name -> ".join(', ', @args)."}:  ". $time, DEBUG );
127                                 if( defined( $resp ) ) {
128                                         #$log->debug( "Calling respond_complete: ". $resp->toString(), INTERNAL );
129                                         $appreq->respond_complete( $resp );
130                                 } else {
131                                         $appreq->status( OpenSRF::DomainObject::oilsConnectStatus->new(
132                                                                 statusCode => STATUS_COMPLETE(),
133                                                                 status => 'Request Complete' ) );
134                                 }
135                         } catch Error with {
136                                 my $e = shift;
137                                 $e = $e->{-text} || $e->message if (ref $e);
138                                 my $sess_id = $session->session_id;
139                                 $session->status(
140                                         OpenSRF::DomainObject::oilsMethodException->new(
141                                                         statusCode      => STATUS_INTERNALSERVERERROR(),
142                                                         status          => " *** Call to [$method_name] failed for session ".
143                                                                            "[$sess_id], thread trace [".$appreq->threadTrace."]:\n$e"
144                                         )
145                                 );
146                         };
147
148
149
150                         # ----------------------------------------------
151
152
153                         # XXX may need this later
154                         # $_->[1] = 1 for (@OpenSRF::AppSession::_CLIENT_CACHE);
155
156                         $in_request--;
157
158                         $log->debug( "Pending Requests: " . scalar(@pending_requests), INTERNAL );
159
160                         # cycle through queued requests
161                         while( my $aref = shift @pending_requests ) {
162                                 $in_request++;
163                                 my $resp;
164                                 try {
165                                         my $start = time;
166                                         my $response = $aref->[2]->run( $aref->[0], @{$aref->[1]} );
167                                         my $time = sprintf '%.3f', time - $start;
168                                         $log->debug( "Method duration for {[".$aref->[2]->name." -> ".join(', ',@{$aref->[1]}).'}:  '.$time, DEBUG );
169
170                                         $appreq = $aref->[0];   
171                                         if( ref( $response ) ) {
172                                                 $log->debug( "Calling respond_complete: ". $response->toString(), INTERNAL );
173                                                 $appreq->respond_complete( $response );
174                                         } else {
175                                                 $appreq->status( OpenSRF::DomainObject::oilsConnectStatus->new(
176                                                                         statusCode => STATUS_COMPLETE(),
177                                                                         status => 'Request Complete' ) );
178                                         }
179                                         $log->debug( "Executed: " . $appreq->threadTrace, DEBUG );
180                                 } catch Error with {
181                                         my $e = shift;
182                                         $session->status(
183                                                 OpenSRF::DomainObject::oilsMethodException->new(
184                                                                 statusCode => STATUS_INTERNALSERVERERROR(),
185                                                                 status => "Call to [".$aref->[2]->name."] faild:  ".$e->{-text}
186                                                 )
187                                         );
188                                 };
189                                 $in_request--;
190                         }
191
192                         return 1;
193                 } 
194                 my $res = OpenSRF::DomainObject::oilsMethodException->new;
195                 $session->send('ERROR', $res);
196                 $session->kill_me;
197                 return 1;
198
199         } else {
200                 $log->debug( "Pushing ". $app_msg->toString ." onto queue", INTERNAL );
201                 $session->push_queue([ $app_msg, $session->last_threadTrace ]);
202         }
203
204         $session->last_message_type('');
205         $session->last_message_api_level('');
206
207         return 1;
208 }
209
210 sub register_method {
211         my $self = shift;
212         my $app = ref($self) || $self;
213         my %args = @_;
214
215
216         throw OpenSRF::DomainObject::oilsMethodException unless ($args{method});
217
218         $args{api_level} ||= 1;
219         $args{stream} ||= 0;
220         $args{remote} ||= 0;
221         $args{package} = $app;                
222         $args{server_class} = server_class();
223         $args{api_name} ||= $args{server_class} . '.' . $args{method};
224
225         unless ($args{object_hint}) {
226                 ($args{object_hint} = $args{package}) =~ s/::/_/go;
227         }
228
229         JSON->register_class_hint( name => $args{package}, hint => $args{object_hint}, type => "hash" );
230
231         $_METHODS[$args{api_level}]{$args{api_name}} = bless \%args => $app;
232
233         __PACKAGE__->register_method(
234                 stream => 0,
235                 api_name => $args{api_name}.'.atomic',
236                 method => 'make_stream_atomic'
237         ) if ($args{stream});
238 }
239
240 sub retrieve_remote_apis {
241         my $session = OpenSRF::AppSession->create('router');
242         try {
243                 $session->connect or OpenSRF::EX::WARN->throw("Connection to router timed out");
244         } catch Error with {
245                 my $e = shift;
246                 $log->debug( "Remote subrequest returned an error:\n". $e );
247                 return undef;
248         } finally {
249                 return undef unless ($session->state == $session->CONNECTED);
250         };
251
252         my $req = $session->request( 'opensrf.router.info.class.list' );
253         my $list = $req->recv;
254
255         if( UNIVERSAL::isa($list,"Error") ) {
256                 throw $list;
257         }
258
259         my $content = $list->content;
260
261         $req->finish;
262         $session->finish;
263         $session->disconnect;
264
265         my %u_list = map { ($_ => 1) } @$content;
266
267         for my $class ( keys %u_list ) {
268                 next if($class eq $server_class);
269                 populate_remote_method_cache($class);
270         }
271 }
272
273 sub populate_remote_method_cache {
274         my $class = shift;
275
276         my $session = OpenSRF::AppSession->create($class);
277         try {
278                 $session->connect or OpenSRF::EX::WARN->throw("Connection to $class timed out");
279
280                 my $req = $session->request( 'opensrf.system.method.all' );
281
282                 while (my $method = $req->recv) {
283                         next if (UNIVERSAL::isa($method, 'Error'));
284
285                         $method = $method->content;
286                         next if ( exists($_METHODS[$$method{api_level}]) &&
287                                 exists($_METHODS[$$method{api_level}]{$$method{api_name}}) );
288                         $method->{remote} = 1;
289                         bless($method, __PACKAGE__ );
290                         $_METHODS[$$method{api_level}]{$$method{api_name}} = $method;
291                 }
292
293                 $req->finish;
294                 $session->finish;
295                 $session->disconnect;
296
297         } catch Error with {
298                 my $e = shift;
299                 $log->debug( "Remote subrequest returned an error:\n". $e );
300                 return undef;
301         };
302 }
303
304 sub method_lookup {             
305         my $self = shift;
306         my $method = shift;
307         my $proto = shift;
308         my $no_recurse = shift || 0;
309         my $no_remote = shift || 0;
310
311         # this instead of " || 1;" above to allow api_level 0
312         $proto = 1 unless (defined $proto);
313
314         my $class = ref($self) || $self;
315
316         $log->debug("Lookup of [$method] by [$class]", DEBUG);
317         $log->debug("Available methods\n".Dumper(\@_METHODS), INTERNAL);
318
319         my $meth;
320         if (__PACKAGE__->thunk) {
321                 for my $p ( reverse(1 .. $proto) ) {
322                         if (exists $_METHODS[$p]{$method}) {
323                                 $meth = $_METHODS[$p]{$method};
324                         }
325                 }
326         } else {
327                 if (exists $_METHODS[$proto]{$method}) {
328                         $meth = $_METHODS[$proto]{$method};
329                 }
330         }
331
332         if (defined $meth) {
333                 $log->debug("Looks like we found [$method]!", DEBUG);
334                 $log->debug("Method object is ".Dumper($meth), INTERNAL);
335                 if($no_remote and $meth->{remote}) {
336                         $log->debug("OH CRAP We're not supposed to return remote methods", WARN);
337                         return undef;
338                 }
339
340         } elsif (!$no_recurse) {
341                 retrieve_remote_apis();
342                 $meth = $self->method_lookup($method,$proto,1);
343         }
344
345         return $meth;
346 }
347
348 sub run {
349         my $self = shift;
350         my $req = shift;
351
352         my $resp;
353         my @params = @_;
354
355         if ( !UNIVERSAL::isa($req, 'OpenSRF::AppRequest') ) {
356                 $log->debug("Creating a SubRequest object", DEBUG);
357                 unshift @params, $req;
358                 $req = OpenSRF::AppSubrequest->new;
359         } else {
360                 $log->debug("This is a top level request", DEBUG);
361         }
362
363         if (!$self->{remote}) {
364                 my $code ||= \&{$self->{package} . '::' . $self->{method}};
365                 $log->debug("Created coderef [$code] for $$self{package}::$$self{method}",DEBUG);
366                 try {
367                         $resp = $code->($self, $req, @params);
368                 } catch Error with {
369                         my $e = shift;
370                         $log->error("Sub $$self{package}::$$self{method} DIED!!!\n\t$e\n".Carp::longmess(), ERROR);
371                         die $e;
372                 };
373
374                 $log->debug("Coderef for [$$self{package}::$$self{method}] has been run", DEBUG);
375
376                 if ( ref($req) and UNIVERSAL::isa($req, 'OpenSRF::AppSubrequest') ) {
377                         $log->debug("A SubRequest object is responding", DEBUG);
378                         $req->respond($resp) if (defined $resp);
379                         $log->debug("... Responding with : " . join(" ",$req->responses), DEBUG);
380                         return $req->responses;
381                 } else {
382                         $log->debug("A top level Request object is responding $resp", DEBUG);
383                         return $resp;
384                 }
385         } else {
386                 my $session = OpenSRF::AppSession->create($self->{server_class});
387                 try {
388                         $session->connect or OpenSRF::EX::WARN->throw("Connection to [$$self{server_class}] timed out");
389                         my $remote_req = $session->request( $self->{api_name}, @params );
390                         while (my $remote_resp = $remote_req->recv) {
391                                 OpenSRF::Utils::Logger->debug("Remote Subrequest Received " . $remote_resp, INTERNAL );
392                                 if( UNIVERSAL::isa($remote_resp,"Error") ) {
393                                         throw $remote_resp;
394                                 }
395                                 $req->respond( $remote_resp->content );
396                         }
397                         $remote_req->finish();
398                         $session->finish();
399
400                 } catch Error with {
401                         my $e = shift;
402                         $log->debug( "Remote subrequest returned an error:\n". $e );
403                         return undef;
404                 };
405
406                 $log->debug( "Remote Subrequest Responses " . join(" ", $req->responses), INTERNAL );
407
408                 return $req->responses;
409         }
410         # huh? how'd we get here...
411         return undef;
412 }
413
414 sub introspect {
415         my $self = shift;
416         my $client = shift;
417         my $method = shift;
418
419         $method = undef if ($self->api_name =~ /all$/o);
420
421         for my $api_level ( reverse(1 .. $#_METHODS) ) {
422                 for my $api_name ( sort keys %{$_METHODS[$api_level]} ) {
423                         if (!$_METHODS[$api_level]{$api_name}{remote}) {
424                                 if (defined($method)) {
425                                         if ($api_name eq $method) {
426                                                 $client->respond( $_METHODS[$api_level]{$api_name} );
427                                         }
428                                 } else {
429                                         $log->debug( "Returning definition for method [$api_name]", INTERNAL );
430                                         $client->respond( $_METHODS[$api_level]{$api_name} );
431                                         $log->debug( "responed with definition for method [$api_name]", INTERNAL );
432                                 }
433                         }
434                 }
435         }
436
437         return undef;
438 }
439 __PACKAGE__->register_method(
440         stream => 1,
441         method => 'introspect',
442         api_name => 'opensrf.system.method.all'
443 );
444
445 __PACKAGE__->register_method(
446         stream => 1,
447         method => 'introspect',
448         argc => 1,
449         api_name => 'opensrf.system.method'
450 );
451
452 sub make_stream_atomic {
453         my $self = shift;
454         my $req = shift;
455         my @args = @_;
456
457         (my $m_name = $self->api_name) =~ s/\.atomic$//o;
458         my @results = $self->method_lookup($m_name)->run(@args);
459
460         if (@results == 1) {
461                 return $results[0];
462         }
463         return \@results;
464 }
465
466
467 1;