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