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