]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/perlmods/OpenSRF/Application.pm
logging and error detection fixups
[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{object_hint} ||= '';                
223         $args{server_class} = server_class();
224         $args{api_name} ||= $args{server_class} . '.' . $args{method};
225
226         JSON->register_class_hint( name => $args{package}, hint => $args{object_hint}, type => "hash" );
227
228         $_METHODS[$args{api_level}]{$args{api_name}} = bless \%args => $app;
229
230         __PACKAGE__->register_method(
231                 stream => 0,
232                 api_name => $args{api_name}.'.atomic',
233                 method => 'make_stream_atomic'
234         ) if ($args{stream});
235 }
236
237 sub retrieve_remote_apis {
238         my $session = OpenSRF::AppSession->create('router');
239         try {
240                 $session->connect or OpenSRF::EX::WARN->throw("Connection to router timed out");
241         } catch Error with {
242                 my $e = shift;
243                 $log->debug( "Remote subrequest returned an error:\n". $e );
244                 return undef;
245         } finally {
246                 return undef unless ($session->state == $session->CONNECTED);
247         };
248
249         my $req = $session->request( 'opensrf.router.info.class.list' );
250         my $list = $req->recv;
251
252         if( UNIVERSAL::isa($list,"Error") ) {
253                 throw $list;
254         }
255
256         my $content = $list->content;
257
258         $req->finish;
259         $session->finish;
260         $session->disconnect;
261
262         my %u_list = map { ($_ => 1) } @$content;
263
264         for my $class ( keys %u_list ) {
265                 next if($class eq $server_class);
266                 populate_remote_method_cache($class);
267         }
268 }
269
270 sub populate_remote_method_cache {
271         my $class = shift;
272
273         my $session = OpenSRF::AppSession->create($class);
274         try {
275                 $session->connect or OpenSRF::EX::WARN->throw("Connection to $class timed out");
276
277                 my $req = $session->request( 'opensrf.system.method.all' );
278
279                 while (my $method = $req->recv) {
280                         next if (UNIVERSAL::isa($method, 'Error'));
281
282                         $method = $method->content;
283                         next if ( exists($_METHODS[$$method{api_level}]) &&
284                                 exists($_METHODS[$$method{api_level}]{$$method{api_name}}) );
285                         $method->{remote} = 1;
286                         bless($method, __PACKAGE__ );
287                         $_METHODS[$$method{api_level}]{$$method{api_name}} = $method;
288                 }
289
290                 $req->finish;
291                 $session->finish;
292                 $session->disconnect;
293
294         } catch Error with {
295                 my $e = shift;
296                 $log->debug( "Remote subrequest returned an error:\n". $e );
297                 return undef;
298         };
299 }
300
301 sub method_lookup {             
302         my $self = shift;
303         my $method = shift;
304         my $proto = shift;
305         my $no_recurse = shift || 0;
306         my $no_remote = shift || 0;
307
308         # this instead of " || 1;" above to allow api_level 0
309         $proto = 1 unless (defined $proto);
310
311         my $class = ref($self) || $self;
312
313         $log->debug("Lookup of [$method] by [$class]", DEBUG);
314         $log->debug("Available methods\n".Dumper(\@_METHODS), INTERNAL);
315
316         my $meth;
317         if (__PACKAGE__->thunk) {
318                 for my $p ( reverse(1 .. $proto) ) {
319                         if (exists $_METHODS[$p]{$method}) {
320                                 $meth = $_METHODS[$p]{$method};
321                         }
322                 }
323         } else {
324                 if (exists $_METHODS[$proto]{$method}) {
325                         $meth = $_METHODS[$proto]{$method};
326                 }
327         }
328
329         if (defined $meth) {
330                 $log->debug("Looks like we found [$method]!", DEBUG);
331                 $log->debug("Method object is ".Dumper($meth), INTERNAL);
332                 if($no_remote and $meth->{remote}) {
333                         $log->debug("OH CRAP We're not supposed to return remote methods", WARN);
334                         return undef;
335                 }
336
337         } elsif (!$no_recurse) {
338                 retrieve_remote_apis();
339                 $meth = $self->method_lookup($method,$proto,1);
340         }
341
342         return $meth;
343 }
344
345 sub run {
346         my $self = shift;
347         my $req = shift;
348
349         my $resp;
350         my @params = @_;
351
352         if ( !UNIVERSAL::isa($req, 'OpenSRF::AppRequest') ) {
353                 $log->debug("Creating a SubRequest object", DEBUG);
354                 unshift @params, $req;
355                 $req = OpenSRF::AppSubrequest->new;
356         } else {
357                 $log->debug("This is a top level request", DEBUG);
358         }
359
360         if (!$self->{remote}) {
361                 my $code ||= \&{$self->{package} . '::' . $self->{method}};
362                 $log->debug("Created coderef [$code] for $$self{package}::$$self{method}",DEBUG);
363                 try {
364                         $resp = $code->($self, $req, @params);
365                 } catch Error with {
366                         my $e = shift;
367                         $log->error("Sub $$self{package}::$$self{method} DIED!!!\n\t$e\n".Carp::longmess(), ERROR);
368                         die $e;
369                 };
370
371                 $log->debug("Coderef for [$$self{package}::$$self{method}] has been run", DEBUG);
372
373                 if ( ref($req) and UNIVERSAL::isa($req, 'OpenSRF::AppSubrequest') ) {
374                         $log->debug("A SubRequest object is responding", DEBUG);
375                         $req->respond($resp) if (defined $resp);
376                         $log->debug("... Responding with : " . join(" ",$req->responses), DEBUG);
377                         return $req->responses;
378                 } else {
379                         $log->debug("A top level Request object is responding $resp", DEBUG);
380                         return $resp;
381                 }
382         } else {
383                 my $session = OpenSRF::AppSession->create($self->{server_class});
384                 try {
385                         $session->connect or OpenSRF::EX::WARN->throw("Connection to [$$self{server_class}] timed out");
386                         my $remote_req = $session->request( $self->{api_name}, @params );
387                         while (my $remote_resp = $remote_req->recv) {
388                                 OpenSRF::Utils::Logger->debug("Remote Subrequest Received " . $remote_resp, INTERNAL );
389                                 if( UNIVERSAL::isa($remote_resp,"Error") ) {
390                                         throw $remote_resp;
391                                 }
392                                 $req->respond( $remote_resp->content );
393                         }
394                         $remote_req->finish();
395                         $session->finish();
396
397                 } catch Error with {
398                         my $e = shift;
399                         $log->debug( "Remote subrequest returned an error:\n". $e );
400                         return undef;
401                 };
402
403                 $log->debug( "Remote Subrequest Responses " . join(" ", $req->responses), INTERNAL );
404
405                 return $req->responses;
406         }
407         # huh? how'd we get here...
408         return undef;
409 }
410
411 sub introspect {
412         my $self = shift;
413         my $client = shift;
414         my $method = shift;
415
416         $method = undef if ($self->api_name =~ /all$/o);
417
418         for my $api_level ( reverse(1 .. $#_METHODS) ) {
419                 for my $api_name ( sort keys %{$_METHODS[$api_level]} ) {
420                         if (!$_METHODS[$api_level]{$api_name}{remote}) {
421                                 if (defined($method)) {
422                                         if ($api_name eq $method) {
423                                                 $client->respond( $_METHODS[$api_level]{$api_name} );
424                                         }
425                                 } else {
426                                         $log->debug( "Returning definition for method [$api_name]", INTERNAL );
427                                         $client->respond( $_METHODS[$api_level]{$api_name} );
428                                         $log->debug( "responed with definition for method [$api_name]", INTERNAL );
429                                 }
430                         }
431                 }
432         }
433
434         return undef;
435 }
436 __PACKAGE__->register_method(
437         stream => 1,
438         method => 'introspect',
439         api_name => 'opensrf.system.method.all'
440 );
441
442 __PACKAGE__->register_method(
443         stream => 1,
444         method => 'introspect',
445         argc => 1,
446         api_name => 'opensrf.system.method'
447 );
448
449 sub make_stream_atomic {
450         my $self = shift;
451         my $req = shift;
452         my @args = @_;
453
454         (my $m_name = $self->api_name) =~ s/\.atomic$//o;
455         my @results = $self->method_lookup($m_name)->run(@args);
456
457         if (@results == 1) {
458                 return $results[0];
459         }
460         return \@results;
461 }
462
463
464 1;