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