]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Utils/ScriptRunner.pm
changed some log func names to be more generic
[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         $self->context->destroy;
93         $self->{_loaded} = {};
94         $self->init;
95 }
96
97 sub load {
98         my( $self, $filename ) = @_;
99         $self->{file} = $filename;
100 }
101
102 sub runs { shift()->{_runs} }
103
104 sub reset_count {
105         my $self = shift;
106         my $count = shift;
107
108         $self->{reset_count} = $count if ($count);
109         return $self->{reset_count};
110 }
111
112 sub run {
113         my $self = shift;
114         my $file = shift();
115
116         my $_real = 0;
117         if(!$file) {
118                 $_real = 1;
119                 $file = $self->{file};
120         }
121
122         my $js = $self->context;
123
124
125
126         $self->refresh_context
127                 if ($self->reset_count && $self->runs > $self->reset_count);
128
129         $self->{_runs}++;
130
131         $file = $self->_find_file($file);
132         $logger->debug("full script file path: $file");
133
134         if( ! open(F, $file) ) {
135                 $logger->error("Error opening script file: $file");
136                 return 0;
137         }
138
139         my $res = '';
140         {       local $/ = undef;
141
142                 $self->insert('environment.result' => {});
143
144                 my $content = <F>;
145                 #print ( "full script is [$content]" );
146
147                 my $s = time();
148                 if( !$js || !$content || !$js->eval($content) ) {
149                         $logger->error("$file Eval failed: $@");  
150                         return 0;
151                 }
152                 $logger->debug("eval of $file took ". sprintf('%0.3f', time - $s) . " seconds");
153
154                 if ($_real) {
155                         $self->insert('__' => {'OILS_RESULT' => ''});
156                         $js->eval($__json_js."__.OILS_RESULT = js2JSON(environment.result);");
157                         $res = $self->retrieve('__.OILS_RESULT');
158                 }
159         }
160
161         close(F);
162         $logger->debug( "script result is [$res]" );
163         return JSON->JSON2perl( $res );
164 }
165
166 sub remove_path { 
167         my( $self, $path ) = @_;
168         if (ref($self)) {
169                 if ($self->{_path}{$path}) {
170                         $self->{_path}{$path} = 0;
171                 }
172                 return $self->{_path}{$path};
173         } else {
174                 if ($_paths{$path}) {
175                         $_paths{$path} = 0;
176                 }
177                 return $_paths{$path};
178         }
179 }
180
181 sub add_path { 
182         my( $self, $path ) = @_;
183         if (ref($self)) {
184                 if (!$self->{_path}{$path}) {
185                         $self->{_path}{$path} = 1;
186                 }
187         } else {
188                 if (!$_paths{$path}) {
189                         $_paths{$path} = 1;
190                 }
191         }
192         return $self;
193 }
194
195 sub _find_file {
196         my $self = shift;
197         my $file = shift;
198         for my $p ( keys %{ $self->{_path} } ) {
199                 next unless ($self->{_path}{$p});
200                 my $full = join('/',$p,$file);
201                 return $full if (-e $full);
202         }
203 }
204
205 sub load_lib { 
206         my( $self, $file ) = @_;
207
208         push @{ $self->{libs} }, $file
209                 if (! grep {$_ eq $file} @{ $self->{libs} });
210
211         if (!$self->{_loaded}{$file} && $self->run( $file )) {
212                 $self->{_loaded}{$file} = 1;
213         }
214         return $self->{_loaded}{$file};
215 }
216
217 sub _js_prop_name {
218         my $name = shift;
219         $name =~ s/^.*\.//o;
220         return $name;
221 }
222
223 sub retrieve {
224         my( $self, $key ) = @_;
225         return $self->context->property_get($key);
226 }
227
228 sub insert_method {
229         my( $self, $obj_key, $meth_name, $sub ) = @_;
230         my $obj = $self->context->object_by_path( $obj_key );
231         $self->context->function_set( $meth_name, $sub, $obj ) if $obj;
232 }
233
234
235 sub insert {
236         my( $self, $key, $val, $RO ) = @_;
237         return unless defined($key);
238
239         if (ref($val) =~ /^Fieldmapper/o) {
240                 $self->insert_fm($key, $val, $RO);
241         } elsif (ref($val) and $val =~ /ARRAY/o) {
242                 $self->insert_array($key, $val, $RO);
243         } elsif (ref($val) and $val =~ /HASH/o) {
244                 $self->insert_hash($key, $val, $RO);
245         } elsif (ref($val) and $val =~ /CODE/o) {
246                 $self->context->function_set( $key, $val );
247         } elsif (!ref($val)) {
248                 if( defined($val) ) {
249                         $self->context->property_by_path(
250                                 $key, $val,
251                                 sub { $val },
252                                 ( !$RO ?
253                                         sub { my( $k, $v ) = @_; $val = $v; } :
254                                         sub{}
255                                 )
256                         );
257                 } else {
258                         $self->context->property_by_path($key, "");
259                 }
260
261         } else {
262                 return 0;
263         }
264
265         return 1;
266 }
267
268 sub insert_fm {
269
270         my( $self, $key, $fm, $RO ) = @_;
271         my $ctx = $self->context;
272         return undef unless ($ctx and $key and $fm);
273         my $o = $ctx->object_by_path($key);
274         
275         for my $f ( $fm->properties ) {
276                 my $val = $fm->$f();
277                 if (ref $val) {
278                         $self->insert("$key.$f", $val);
279                 } else {
280                         $ctx->property_by_path(
281                                 "$key.$f",
282                                 $val,
283                                 sub {
284                                         my $k = _js_prop_name(shift());
285                                         $fm->$k();
286                                 }, 
287
288                                 ( !$RO ? 
289                                         sub {
290                                                 my $k = _js_prop_name(shift());
291                                                 $fm->ischanged(1);
292                                                 $fm->$k(@_);
293                                         } :
294                                         sub {}
295                                 )
296                         );
297                 }
298         }
299 }
300
301 sub insert_hash {
302
303         my( $self, $key, $hash, $RO ) = @_;
304         my $ctx = $self->context;
305         return undef unless ($ctx and $key and $hash);
306         $ctx->object_by_path($key);
307         
308         for my $k ( keys %$hash ) {
309                 my $v = $hash->{$k};
310                 if (ref $v) {
311                         $self->insert("$key.$k", $v);
312                 } else {
313                         $ctx->property_by_path(
314                                 "$key.$k", $v,
315                                 sub { $hash->{_js_prop_name(shift())} },
316                                 ( !$RO ? 
317                                         sub {
318                                                 my( $hashkey, $val ) = @_;
319                                                 $hash->{_js_prop_name($hashkey)} = $val;
320                                         } :
321                                         sub {}
322                                 )
323                         );
324                 }
325         }
326 }
327
328 my $__array_id = 0;
329 sub insert_array {
330
331         my( $self, $key, $array ) = @_;
332         my $ctx = $self->context;
333         return undef unless ($ctx and $key and $array);
334
335         my $a = $ctx->array_by_path($key);
336         
337         my $ind = 0;
338         for my $v ( @$array ) {
339                 if (ref $v) {
340                         my $tmp_index = $__array_id++;
341                         my $elobj = $ctx->object_by_path('__tmp_arr_el'.$tmp_index);
342                         $self->insert('__tmp_arr_el'.$tmp_index, $v);
343                         $ctx->array_set_element_as_object( $a, $ind, $elobj );
344                 } else {
345                         $ctx->array_set_element( $a, $ind, $v ) if defined($v);
346                 }
347                 $ind++;
348         }
349 }
350
351 sub _xmlhttprequest_send {
352         my $self = shift;
353         my $id = shift;
354         my $method = shift;
355         my $url = shift;
356         my $blocking = shift;
357         my $headerlist = shift;
358         my $data = shift;
359
360         my $ctx = $self->context;
361
362         # just so perl has access to it...
363         $ctx->object_by_path('__xmlhttpreq_hash.id'.$id);
364
365         my $headers = new HTTP::Headers;
366         my @lines = split(/\n/so, $headerlist);
367         for my $line (@lines) {
368                 if ($line =~ /^(.+?)|(.+)$/o) {
369                         $headers->header($1 => $2);
370                 }
371         }
372
373         my $ua = LWP::UserAgent->new;
374         $ua->agent("OpenILS/0.1");
375
376         my $req = HTTP::Request->new($method => $url => $headers => $data);
377         my $res = $ua->request($req);
378
379         if ($res->is_success) {
380                 
381                 $ctx->property_by_path('__xmlhttpreq_hash.id'.$id.'.responseText', $res->content);
382                 $ctx->property_by_path('__xmlhttpreq_hash.id'.$id.'.readyState', 4);
383                 $ctx->property_by_path('__xmlhttpreq_hash.id'.$id.'.statusText', $res->status_line);
384                 $ctx->property_by_path('__xmlhttpreq_hash.id'.$id.'.status', $res->code);
385
386         }
387                 
388 }
389
390 our %_jsonopensrfrequest_cache = ();
391
392 sub _jsonopensrfrequest_connect {
393         my $self = shift;
394         my $id = shift;
395         my $service = shift;
396
397         my $ctx = $self->context;
398         $ctx->object_by_path('__jsonopensrfreq_hash.id'.$id);
399
400         my $ses = $_jsonopensrfrequest_cache{$id} ||
401                         do { $_jsonopensrfrequest_cache{$id} = OpenSRF::AppSession->create($service) };
402
403         if($ses->connect) {
404                 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.connected', 1);
405         } else {
406                 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.connected', 0);
407         }
408 }
409
410 sub _jsonopensrfrequest_disconnect {
411         my $self = shift;
412         my $id = shift;
413
414         my $ctx = $self->context;
415         $ctx->object_by_path('__jsonopensrfreq_hash.id'.$id);
416
417         my $ses = $_jsonopensrfrequest_cache{$id};
418         return unless $ses;
419
420         $ses->disconnect;
421 }
422
423 sub _jsonopensrfrequest_finish {
424         my $self = shift;
425         my $id = shift;
426
427         my $ctx = $self->context;
428         $ctx->object_by_path('__jsonopensrfreq_hash.id'.$id);
429
430         my $ses = $_jsonopensrfrequest_cache{$id};
431         return unless $ses;
432
433         $ses->finish;
434         delete $_jsonopensrfrequest_cache{$id};
435 }
436
437 sub _jsonopensrfrequest_send {
438         my $self = shift;
439         my $id = shift;
440         my $service = shift;
441         my $method = shift;
442         my $blocking = shift;
443         my $params = shift;
444
445         my @p = @{ JSON->JSON2perl($params) };
446
447         my $ctx = $self->context;
448
449         # just so perl has access to it...
450         $ctx->object_by_path('__jsonopensrfreq_hash.id'.$id);
451
452         my $ses = $_jsonopensrfrequest_cache{$id} ||
453                         do { $_jsonopensrfrequest_cache{$id} = OpenSRF::AppSession->create($service) };
454         my $req = $ses->request($method,@p);
455
456         $req->wait_complete;
457         if (!$req->failed) {
458                 my $res = $req->recv->content;
459                 
460                 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.responseText', JSON->perl2JSON($res));
461                 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.readyState', 4);
462                 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.statusText', 'OK');
463                 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.status', '200');
464
465         } else {
466                 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.responseText', '');
467                 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.readyState', 4);
468                 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.statusText', $req->failed->status );
469                 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.status', $req->failed->statusCode );
470         }
471
472         $req->finish;
473                 
474 }
475
476 sub _parse_xml_string {
477         my $self = shift;
478         my $string = shift;
479         my $key = shift;
480
481
482         my $doc;
483         my $s = 0;
484         try {
485                 $doc = XML::LibXML->new->parse_string( $string );
486                 $s = 1;
487         } catch Error with {
488                 my $e = shift;
489                 warn "Could not parse document: $e\n";
490         };
491         return unless ($s);
492
493         _JS_DOM($self->context, $key, $doc);
494 }
495
496 sub _JS_DOM {
497         my $ctx = shift;
498         my $key = shift;
499         my $node = shift;
500
501         if ($node->nodeType == 9) {
502                 $node = $node->documentElement;
503
504                 my $n = $node->nodeName;
505                 my $ns = $node->namespaceURI;
506                 $ns =~ s/'/\'/gso if ($ns);
507                 $ns = "'$ns'" if ($ns);
508                 $ns = 'null' unless ($ns);
509                 $n =~ s/'/\'/gso;
510
511                 #warn("$key = DOMImplementation().createDocument($ns,'$n');");
512                 $ctx->eval("$key = new DOMImplementation().createDocument($ns,'$n');");
513
514                 $key = $key.'.documentElement';
515         }
516
517         for my $a ($node->attributes) {
518                 my $n = $a->nodeName;
519                 my $v = $a->value;
520                 $n =~ s/'/\'/gso;
521                 $v =~ s/'/\'/gso;
522                 #warn("$key.setAttribute('$n','$v');");
523                 $ctx->eval("$key.setAttribute('$n','$v');");
524
525         }
526
527         my $k = 0;
528         for my $c ($node->childNodes) {
529                 if ($c->nodeType == 1) {
530                         my $n = $c->nodeName;
531                         my $ns = $node->namespaceURI;
532
533                         $n =~ s/'/\'/gso;
534                         $ns =~ s/'/\'/gso if ($ns);
535                         $ns = "'$ns'" if ($ns);
536                         $ns = 'null' unless ($ns);
537
538                         #warn("$key.appendChild($key.ownerDocument.createElementNS($ns,'$n'));");
539                         $ctx->eval("$key.appendChild($key.ownerDocument.createElementNS($ns,'$n'));");
540                         _JS_DOM($ctx, "$key.childNodes.item($k)",$c);
541
542                 } elsif ($c->nodeType == 3) {
543                         my $n = $c->data;
544                         $n =~ s/'/\'/gso;
545                         #warn("$key.appendChild($key.ownerDocument.createTextNode('$n'));");
546                         #warn("path is $key.item($k);");
547                         $ctx->eval("$key.appendChild($key.ownerDocument.createTextNode('$n'));");
548
549                 } elsif ($c->nodeType == 4) {
550                         my $n = $c->data;
551                         $n =~ s/'/\'/gso;
552                         #warn("$key.appendChild($key.ownerDocument.createCDATASection('$n'));");
553                         $ctx->eval("$key.appendChild($key.ownerDocument.createCDATASection('$n'));");
554
555                 } elsif ($c->nodeType == 8) {
556                         my $n = $c->data;
557                         $n =~ s/'/\'/gso;
558                         #warn("$key.appendChild($key.ownerDocument.createComment('$n'));");
559                         $ctx->eval("$key.appendChild($key.ownerDocument.createComment('$n'));");
560
561                 } else {
562                         warn "ACK! I don't know how to handle node type ".$c->nodeType;
563                 }
564                 
565
566                 $k++;
567         }
568
569         return 1;
570 }
571
572
573
574 1;
575
576 __DATA__
577
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