]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Utils/ScriptRunner.pm
added cleanup method to script runner
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Utils / ScriptRunner.pm
1 package OpenILS::Utils::ScriptRunner;
2 use strict; use warnings;
3 use OpenSRF::Utils::Logger qw(:logger);
4 use OpenSRF::EX qw(:try);
5 use JSON;
6 use JavaScript::SpiderMonkey;
7 use LWP::UserAgent;
8 use XML::LibXML;
9 use Time::HiRes qw/time/;
10 use vars qw/%_paths $__json_js/;
11
12 { local $/ = undef; $__json_js = <DATA>; }
13
14 sub DESTROY {
15         my $self = shift;
16         $logger->info("script_runner destroying self: $self");
17 }
18
19 sub cleanup {
20         my $runner = shift;
21         $runner->context->destroy;
22         delete($$runner{$_}) for (keys %$runner);
23 }
24
25 sub new {
26         my $class = shift;
27         my %params = @_;
28         $class = ref($class) || $class;
29         $params{paths} ||= [];
30         $params{reset_count} ||= 0;
31
32         my $self = bless {      file => $params{file},
33                                 libs => $params{libs},
34                                 reset_count => $params{reset_count},
35                                 _runs => 0,
36                                 _path => {%_paths} } => $class;
37
38         $self->add_path($_) for @{$params{paths}};
39         return $self->init; 
40 }
41
42 sub context {
43         my( $self, $context ) = @_;
44         $self->{ctx} = $context if $context;
45         return $self->{ctx};
46 }
47
48 sub init {
49         my $self = shift;
50         $self->context( new JavaScript::SpiderMonkey );
51         $self->context->init();
52
53         $self->{_runs} = 0;
54
55         # eating our own dog food with insert
56         $self->insert(log_stdout        => sub { print "@_\n"; } );
57         $self->insert(log_stderr        => sub { warn "@_\n"; } );
58         $self->insert(log_activity      => sub { $logger->activity("script_runner: @_"); return 1;} );
59         $self->insert(log_error         => sub { $logger->error("script_runner: @_"); return 1;} );
60         $self->insert(log_warn          => sub { $logger->warn("script_runner: @_"); return 1;} );
61         $self->insert(log_info          => sub { $logger->info("script_runner: @_"); return 1;} );
62         $self->insert(log_debug         => sub { $logger->debug("script_runner: @_"); return 1;} );
63         $self->insert(log_internal      => sub { $logger->internal("script_runner: @_"); return 1;} );
64         $self->insert(debug             => sub { $logger->debug("script_runner: @_"); return 1;} );
65         $self->insert(alert             => sub { $logger->warn("script_runner: @_"); return 1;} );
66         $self->insert(load_lib          => sub { $self->load_lib(@_); return 1;});
67
68         # OpenSRF support functions
69         $self->insert(
70                 _OILS_FUNC_jsonopensrfrequest_send =>
71                         sub { $self->_jsonopensrfrequest_send(@_); }
72         );
73         $self->insert(
74                 _OILS_FUNC_jsonopensrfrequest_connect =>
75                         sub { $self->_jsonopensrfrequest_connect(@_); }
76         );
77         $self->insert(
78                 _OILS_FUNC_jsonopensrfrequest_disconnect =>
79                         sub { $self->_jsonopensrfrequest_disconnect(@_); }
80         );
81         $self->insert(
82                 _OILS_FUNC_jsonopensrfrequest_finish =>
83                         sub { $self->_jsonopensrfrequest_finish(@_); }
84         );
85
86         # XML support functions
87         $self->insert(
88                 _OILS_FUNC_xmlhttprequest_send  =>
89                         sub { $self->_xmlhttprequest_send(@_); }
90         );
91         $self->insert(
92                 _OILS_FUNC_xml_parse_string     =>
93                         sub { $self->_parse_xml_string(@_); }
94         );
95         
96         while ( my $e = shift @{$self->{_env}} ) {
97                 $self->insert( @$e{ qw/key value readonly/ } => 1 );
98         }
99
100         while ( my $e = shift @{$self->{_methods}} ) {
101                 $self->insert_method( @$e{ qw/key name meth/ } => 1 );
102         }
103
104         $self->load_lib($_) for @{$self->{libs}};
105
106         return $self;
107 }
108
109 sub refresh_context {
110         my $self = shift;
111         $logger->debug("Refreshing JavaScript Context...");
112         $self->context->destroy;
113         $logger->debug("Context destroyed");
114         $self->{_loaded} = {};
115         $logger->debug("Loaded scripts removed");
116         $self->init;
117         $logger->debug("New Context initialized");
118         return $self;
119 }
120
121 sub load {
122         my( $self, $filename ) = @_;
123         $self->{file} = $filename;
124 }
125
126 sub runs { shift()->{_runs} }
127
128 sub reset_count {
129         my $self = shift;
130         my $count = shift;
131
132         $self->{reset_count} = $count if ($count);
133         return $self->{reset_count};
134 }
135
136 sub run {
137         my $self = shift;
138         my $file = shift();
139
140         my $_real = 0;
141         if(!$file) {
142                 $_real = 1;
143                 $file = $self->{file};
144         }
145
146         $self->refresh_context
147                 if ($self->reset_count && $self->runs > $self->reset_count);
148
149         $self->{_runs}++ if ($_real);
150
151         $file = $self->_find_file($file);
152         $logger->debug("full script file path: $file");
153
154         if( ! open(F, $file) ) {
155                 $logger->error("Error opening script file: $file");
156                 return 0;
157         }
158
159         my $js = $self->context;
160
161         my $res = '';
162         {       local $/ = undef;
163
164                 $self->insert('environment.result' => {});
165
166                 my $content = <F>;
167                 #print ( "full script is [$content]" );
168
169                 my $s = time();
170                 if( !$js || !$content || !$js->eval($content) ) {
171                         $logger->error("$file Eval failed: $@");  
172                         return 0;
173                 }
174                 $logger->debug("eval of $file took ". sprintf('%0.3f', time - $s) . " seconds");
175
176                 if ($_real) {
177                         $self->insert('__' => {'OILS_RESULT' => ''});
178                         $js->eval($__json_js."__.OILS_RESULT = js2JSON(environment.result);");
179                         $res = $self->retrieve('__.OILS_RESULT');
180                 }
181         }
182
183         close(F);
184         $logger->debug( "script result is [$res]" );
185         return JSON->JSON2perl( $res );
186 }
187
188 sub remove_path { 
189         my( $self, $path ) = @_;
190         if (ref($self)) {
191                 if ($self->{_path}{$path}) {
192                         $self->{_path}{$path} = 0;
193                 }
194                 return $self->{_path}{$path};
195         } else {
196                 if ($_paths{$path}) {
197                         $_paths{$path} = 0;
198                 }
199                 return $_paths{$path};
200         }
201 }
202
203 sub add_path { 
204         my( $self, $path ) = @_;
205         if (ref($self)) {
206                 if (!$self->{_path}{$path}) {
207                         $self->{_path}{$path} = 1;
208                 }
209         } else {
210                 if (!$_paths{$path}) {
211                         $_paths{$path} = 1;
212                 }
213         }
214         return $self;
215 }
216
217 sub _find_file {
218         my $self = shift;
219         my $file = shift;
220         for my $p ( keys %{ $self->{_path} } ) {
221                 next unless ($self->{_path}{$p});
222                 my $full = join('/',$p,$file);
223                 return $full if (-e $full);
224         }
225 }
226
227 sub load_lib { 
228         my( $self, $file ) = @_;
229
230         push @{ $self->{libs} }, $file
231                 if (! grep {$_ eq $file} @{ $self->{libs} });
232
233         if (!$self->{_loaded}{$file}) {
234                 $self->run( $file );
235                 $self->{_loaded}{$file} = 1;
236         }
237         return $self->{_loaded}{$file};
238 }
239
240 sub _js_prop_name {
241         my $name = shift;
242         $name =~ s/^.*\.//o;
243         return $name;
244 }
245
246 sub retrieve {
247         my( $self, $key ) = @_;
248         return $self->context->property_get($key);
249 }
250
251 sub insert_method {
252         my( $self, $obj_key, $meth_name, $sub, $stop) = @_;
253
254         push @{$self->{_methods}}, { key => $obj_key => name => $meth_name, meth => $sub } unless ($stop);
255         
256         my $obj = $self->context->object_by_path( $obj_key );
257         $self->context->function_set( $meth_name, $sub, $obj ) if $obj;
258 }
259
260
261 sub insert {
262         my( $self, $key, $val, $RO, $stop ) = @_;
263         return unless defined($key);
264
265         push @{$self->{_env}}, { key => $key => value => $val, readonly => $RO } unless ($stop);
266
267         if (ref($val) =~ /^Fieldmapper/o) {
268                 $self->insert_fm($key, $val, $RO);
269         } elsif (ref($val) and $val =~ /ARRAY/o) {
270                 $self->insert_array($key, $val, $RO);
271         } elsif (ref($val) and $val =~ /HASH/o) {
272                 $self->insert_hash($key, $val, $RO);
273         } elsif (ref($val) and $val =~ /CODE/o) {
274                 $self->context->function_set( $key, $val );
275         } elsif (!ref($val)) {
276                 if( defined($val) ) {
277                         $self->context->property_by_path(
278                                 $key, $val,
279                                 ( !$RO ?  (sub { $val }, sub { my( $k, $v ) = @_; $val = $v; }) : () )
280                         );
281                 } else {
282                         $self->context->property_by_path($key, "");
283                 }
284
285         } else {
286                 return 0;
287         }
288
289         return 1;
290 }
291
292 sub insert_fm {
293
294         my( $self, $key, $fm, $RO ) = @_;
295         my $ctx = $self->context;
296         return undef unless ($ctx and $key and $fm);
297         my $o = $ctx->object_by_path($key);
298         
299         for my $f ( $fm->properties ) {
300                 my $val = $fm->$f();
301                 if (ref $val) {
302                         $self->insert("$key.$f", $val);
303                 } else {
304                         $ctx->property_by_path(
305                                 "$key.$f",
306                                 $val,
307                                 ( !$RO ? 
308                                         (sub {
309                                                 my $k = _js_prop_name(shift());
310                                                 $fm->$k();
311                                         }, 
312                                         sub {
313                                                 my $k = _js_prop_name(shift());
314                                                 $fm->ischanged(1);
315                                                 $fm->$k(@_);
316                                         }) :
317                                         ()
318                                 )
319                         );
320                 }
321         }
322 }
323
324 sub insert_hash {
325
326         my( $self, $key, $hash, $RO ) = @_;
327         my $ctx = $self->context;
328         return undef unless ($ctx and $key and $hash);
329         $ctx->object_by_path($key);
330         
331         for my $k ( keys %$hash ) {
332                 my $v = $hash->{$k};
333                 if (ref $v) {
334                         $self->insert("$key.$k", $v);
335                 } else {
336                         $ctx->property_by_path(
337                                 "$key.$k", $v,
338                                 ( !$RO ? 
339                                         (sub { $hash->{_js_prop_name(shift())} },
340                                         sub {
341                                                 my( $hashkey, $val ) = @_;
342                                                 $hash->{_js_prop_name($hashkey)} = $val;
343                                         }) :
344                                         ()
345                                 )
346                         );
347                 }
348         }
349 }
350
351 my $__array_id = 0;
352 sub insert_array {
353
354         my( $self, $key, $array ) = @_;
355         my $ctx = $self->context;
356         return undef unless ($ctx and $key and $array);
357
358         my $a = $ctx->array_by_path($key);
359         
360         my $ind = 0;
361         for my $v ( @$array ) {
362                 if (ref $v) {
363                         my $tmp_index = $__array_id++;
364                         my $elobj = $ctx->object_by_path('__tmp_arr_el'.$tmp_index);
365                         $self->insert('__tmp_arr_el'.$tmp_index, $v);
366                         $ctx->array_set_element_as_object( $a, $ind, $elobj );
367                 } else {
368                         $ctx->array_set_element( $a, $ind, $v ) if defined($v);
369                 }
370                 $ind++;
371         }
372 }
373
374 sub _xmlhttprequest_send {
375         my $self = shift;
376         my $id = shift;
377         my $method = shift;
378         my $url = shift;
379         my $blocking = shift;
380         my $headerlist = shift;
381         my $data = shift;
382
383         my $ctx = $self->context;
384
385         # just so perl has access to it...
386         $ctx->object_by_path('__xmlhttpreq_hash.id'.$id);
387
388         my $headers = new HTTP::Headers;
389         my @lines = split(/\n/so, $headerlist);
390         for my $line (@lines) {
391                 if ($line =~ /^(.+?)|(.+)$/o) {
392                         $headers->header($1 => $2);
393                 }
394         }
395
396         my $ua = LWP::UserAgent->new;
397         $ua->agent("OpenILS/0.1");
398
399         my $req = HTTP::Request->new($method => $url => $headers => $data);
400         my $res = $ua->request($req);
401
402         if ($res->is_success) {
403                 
404                 $ctx->property_by_path('__xmlhttpreq_hash.id'.$id.'.responseText', $res->content);
405                 $ctx->property_by_path('__xmlhttpreq_hash.id'.$id.'.readyState', 4);
406                 $ctx->property_by_path('__xmlhttpreq_hash.id'.$id.'.statusText', $res->status_line);
407                 $ctx->property_by_path('__xmlhttpreq_hash.id'.$id.'.status', $res->code);
408
409         }
410                 
411 }
412
413 our %_jsonopensrfrequest_cache = ();
414
415 sub _jsonopensrfrequest_connect {
416         my $self = shift;
417         my $id = shift;
418         my $service = shift;
419
420         my $ctx = $self->context;
421         $ctx->object_by_path('__jsonopensrfreq_hash.id'.$id);
422
423         my $ses = $_jsonopensrfrequest_cache{$id} ||
424                         do { $_jsonopensrfrequest_cache{$id} = OpenSRF::AppSession->create($service) };
425
426         if($ses->connect) {
427                 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.connected', 1);
428         } else {
429                 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.connected', 0);
430         }
431 }
432
433 sub _jsonopensrfrequest_disconnect {
434         my $self = shift;
435         my $id = shift;
436
437         my $ctx = $self->context;
438         $ctx->object_by_path('__jsonopensrfreq_hash.id'.$id);
439
440         my $ses = $_jsonopensrfrequest_cache{$id};
441         return unless $ses;
442
443         $ses->disconnect;
444 }
445
446 sub _jsonopensrfrequest_finish {
447         my $self = shift;
448         my $id = shift;
449
450         my $ctx = $self->context;
451         $ctx->object_by_path('__jsonopensrfreq_hash.id'.$id);
452
453         my $ses = $_jsonopensrfrequest_cache{$id};
454         return unless $ses;
455
456         $ses->finish;
457         delete $_jsonopensrfrequest_cache{$id};
458 }
459
460 sub _jsonopensrfrequest_send {
461         my $self = shift;
462         my $id = shift;
463         my $service = shift;
464         my $method = shift;
465         my $blocking = shift;
466         my $params = shift;
467
468         my @p = @{ JSON->JSON2perl($params) };
469
470         my $ctx = $self->context;
471
472         # just so perl has access to it...
473         $ctx->object_by_path('__jsonopensrfreq_hash.id'.$id);
474
475         my $ses = $_jsonopensrfrequest_cache{$id} ||
476                         do { $_jsonopensrfrequest_cache{$id} = OpenSRF::AppSession->create($service) };
477         my $req = $ses->request($method,@p);
478
479         $req->wait_complete;
480         if (!$req->failed) {
481                 my $res = $req->recv->content;
482                 
483                 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.responseText', JSON->perl2JSON($res));
484                 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.readyState', 4);
485                 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.statusText', 'OK');
486                 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.status', '200');
487
488         } else {
489                 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.responseText', '');
490                 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.readyState', 4);
491                 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.statusText', $req->failed->status );
492                 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.status', $req->failed->statusCode );
493         }
494
495         $req->finish;
496                 
497 }
498
499 sub _parse_xml_string {
500         my $self = shift;
501         my $string = shift;
502         my $key = shift;
503
504
505         my $doc;
506         my $s = 0;
507         try {
508                 $doc = XML::LibXML->new->parse_string( $string );
509                 $s = 1;
510         } catch Error with {
511                 my $e = shift;
512                 warn "Could not parse document: $e\n";
513         };
514         return unless ($s);
515
516         _JS_DOM($self->context, $key, $doc);
517 }
518
519 sub _JS_DOM {
520         my $ctx = shift;
521         my $key = shift;
522         my $node = shift;
523
524         if ($node->nodeType == 9) {
525                 $node = $node->documentElement;
526
527                 my $n = $node->nodeName;
528                 my $ns = $node->namespaceURI;
529                 $ns =~ s/'/\'/gso if ($ns);
530                 $ns = "'$ns'" if ($ns);
531                 $ns = 'null' unless ($ns);
532                 $n =~ s/'/\'/gso;
533
534                 #warn("$key = DOMImplementation().createDocument($ns,'$n');");
535                 $ctx->eval("$key = new DOMImplementation().createDocument($ns,'$n');");
536
537                 $key = $key.'.documentElement';
538         }
539
540         for my $a ($node->attributes) {
541                 my $n = $a->nodeName;
542                 my $v = $a->value;
543                 $n =~ s/'/\'/gso;
544                 $v =~ s/'/\'/gso;
545                 #warn("$key.setAttribute('$n','$v');");
546                 $ctx->eval("$key.setAttribute('$n','$v');");
547
548         }
549
550         my $k = 0;
551         for my $c ($node->childNodes) {
552                 if ($c->nodeType == 1) {
553                         my $n = $c->nodeName;
554                         my $ns = $node->namespaceURI;
555
556                         $n =~ s/'/\'/gso;
557                         $ns =~ s/'/\'/gso if ($ns);
558                         $ns = "'$ns'" if ($ns);
559                         $ns = 'null' unless ($ns);
560
561                         #warn("$key.appendChild($key.ownerDocument.createElementNS($ns,'$n'));");
562                         $ctx->eval("$key.appendChild($key.ownerDocument.createElementNS($ns,'$n'));");
563                         _JS_DOM($ctx, "$key.childNodes.item($k)",$c);
564
565                 } elsif ($c->nodeType == 3) {
566                         my $n = $c->data;
567                         $n =~ s/'/\'/gso;
568                         #warn("$key.appendChild($key.ownerDocument.createTextNode('$n'));");
569                         #warn("path is $key.item($k);");
570                         $ctx->eval("$key.appendChild($key.ownerDocument.createTextNode('$n'));");
571
572                 } elsif ($c->nodeType == 4) {
573                         my $n = $c->data;
574                         $n =~ s/'/\'/gso;
575                         #warn("$key.appendChild($key.ownerDocument.createCDATASection('$n'));");
576                         $ctx->eval("$key.appendChild($key.ownerDocument.createCDATASection('$n'));");
577
578                 } elsif ($c->nodeType == 8) {
579                         my $n = $c->data;
580                         $n =~ s/'/\'/gso;
581                         #warn("$key.appendChild($key.ownerDocument.createComment('$n'));");
582                         $ctx->eval("$key.appendChild($key.ownerDocument.createComment('$n'));");
583
584                 } else {
585                         warn "ACK! I don't know how to handle node type ".$c->nodeType;
586                 }
587                 
588
589                 $k++;
590         }
591
592         return 1;
593 }
594
595
596
597 1;
598
599 __DATA__
600
601 // in case we run on an implimentation that doesn't have "undefined";
602 var undefined;
603
604 function Cast (obj, class_constructor) {
605         try {
606                 if (eval(class_constructor + '["_isfieldmapper"]')) {
607                         obj = eval("new " + class_constructor + "(obj)");
608                 }
609         } catch( E ) {
610                 alert( E + "\n");
611         } finally {
612                 return obj;
613         }
614 }
615
616 function JSON2js (json) {
617
618         json = String(json).replace( /\/\*--\s*S\w*?\s*?\s+\w+\s*--\*\//g, 'Cast(');
619         json = String(json).replace( /\/\*--\s*E\w*?\s*?\s+(\w+)\s*--\*\//g, ', "$1")');
620
621         var obj;
622         if (json != '') {
623                 try {
624                         eval( 'obj = ' + json );
625                 } catch(E) {
626                         debug("Error building JSON object with string " + E + "\nString:\n" + json );
627                         return null;
628                 }
629         }
630         return obj;
631 }
632
633
634 function object2Array(obj) {
635         if( obj == null ) return null;
636
637         var arr = new Array();
638         for( var i  = 0; i < obj.length; i++ ) {
639                 arr[i] = obj[i];
640         }
641         return arr;
642 }
643
644
645 function js2JSON(arg) {
646         return _js2JSON(arg);
647 }
648
649 function _js2JSON(arg) {
650         var i, o, u, v;
651
652                 switch (typeof arg) {
653                         case 'object':
654         
655                                 if(arg) {
656         
657                                         if (arg._isfieldmapper) { /* magi-c-ast for fieldmapper objects */
658         
659                                                 if( arg.a.constructor != Array ) {
660                                                         var arr = new Array();
661                                                         for( var i  = 0; i < arg.a.length; i++ ) {
662                                                                 if( arg.a[i] == null ) {
663                                                                         arr[i] = null; continue;
664                                                                 }
665         
666                                                                 if( typeof arg.a[i] != 'object' ) { 
667                                                                         arr[i] = arg.a[i];
668         
669                                                                 } else if( typeof arg.a[i] == 'object' 
670                                                                                         && arg.a[i]._isfieldmapper) {
671         
672                                                                         arr[i] = arg.a[i];
673         
674                                                                 } else {
675                                                                         arr[i] = object2Array(arg.a[i]);                
676                                                                 }
677                                                         }
678                                                         arg.a = arr;
679                                                 }
680         
681                                                 return "/*--S " + arg.classname + " --*/" + js2JSON(arg.a) + "/*--E " + arg.classname + " --*/";
682         
683                                         } else {
684         
685                                                 if (arg.constructor == Array) {
686                                                         o = '';
687                                                         for (i = 0; i < arg.length; ++i) {
688                                                                 v = js2JSON(arg[i]);
689                                                                 if (o) {
690                                                                         o += ',';
691                                                                 }
692                                                                 if (v !== u) {
693                                                                         o += v;
694                                                                 } else {
695                                                                         o += 'null';
696                                                                 }
697                                                         }
698                                                         return '[' + o + ']';
699         
700                                                 } else if (typeof arg.toString != 'undefined') {
701                                                         o = '';
702                                                         for (i in arg) {
703                                                                 v = js2JSON(arg[i]);
704                                                                 if (v !== u) {
705                                                                         if (o) {
706                                                                                 o += ',';
707                                                                         }
708                                                                         o += js2JSON(i) + ':' + v;
709                                                                 }
710                                                         }
711         
712                                                         o = '{' + o + '}';
713                                                         return o;
714         
715                                                 } else {
716                                                         return;
717                                                 }
718                                         }
719                                 }
720                                 return 'null';
721         
722                         case 'unknown':
723                         case 'number':
724                                 if( isNaN(arg) ) throw "JSON.js encountered NaN in js2JSON()";
725                                 return arg;
726         
727                         case 'undefined':
728                         case 'function':
729                                 return u;
730         
731                         case 'string':
732                         default:
733                                 return '"' + String(arg).replace(/(["\\])/g, '\\$1') + '"';
734                 }
735
736 }
737