1 package OpenILS::Utils::ScriptRunner;
2 use strict; use warnings;
3 use OpenSRF::Utils::Logger qw(:logger);
4 use OpenSRF::EX qw(:try);
6 use JavaScript::SpiderMonkey;
9 use Time::HiRes qw/time/;
10 use vars qw/%_paths $__json_js/;
12 { local $/ = undef; $__json_js = <DATA>; }
17 $class = ref($class) || $class;
18 $params{paths} ||= [];
19 $params{reset_count} ||= 0;
21 my $self = bless { file => $params{file},
22 libs => $params{libs},
23 reset_count => $params{reset_count},
25 _path => {%_paths} } => $class;
27 $self->add_path($_) for @{$params{paths}};
32 my( $self, $context ) = @_;
33 $self->{ctx} = $context if $context;
39 $self->context( new JavaScript::SpiderMonkey );
40 $self->context->init();
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(@_); });
58 # OpenSRF support functions
60 _OILS_FUNC_jsonopensrfrequest_send =>
61 sub { $self->_jsonopensrfrequest_send(@_); }
64 _OILS_FUNC_jsonopensrfrequest_connect =>
65 sub { $self->_jsonopensrfrequest_connect(@_); }
68 _OILS_FUNC_jsonopensrfrequest_disconnect =>
69 sub { $self->_jsonopensrfrequest_disconnect(@_); }
72 _OILS_FUNC_jsonopensrfrequest_finish =>
73 sub { $self->_jsonopensrfrequest_finish(@_); }
76 # XML support functions
78 _OILS_FUNC_xmlhttprequest_send =>
79 sub { $self->_xmlhttprequest_send(@_); }
82 _OILS_FUNC_xml_parse_string =>
83 sub { $self->_parse_xml_string(@_); }
86 $self->load_lib($_) for @{$self->{libs}};
93 $self->context->destroy;
94 $self->{_loaded} = {};
99 my( $self, $filename ) = @_;
100 $self->{file} = $filename;
103 sub runs { shift()->{_runs} }
109 $self->{reset_count} = $count if ($count);
110 return $self->{reset_count};
120 $file = $self->{file};
123 my $js = $self->context;
127 $self->refresh_context
128 if ($self->reset_count && $self->runs > $self->reset_count);
132 $file = $self->_find_file($file);
133 $logger->debug("full script file path: $file");
135 if( ! open(F, $file) ) {
136 $logger->error("Error opening script file: $file");
143 $self->insert('environment.result' => {});
146 print ( "full script is [$content]" );
149 if( !$js || !$content || !$js->eval($content) ) {
150 $logger->error("$file Eval failed: $@");
153 $logger->debug("eval of $file took ". sprintf('%0.3f', time - $s) . " seconds");
156 $self->insert('__' => {'OILS_RESULT' => ''});
157 $js->eval($__json_js."__.OILS_RESULT = js2JSON(environment.result);");
158 $res = $self->retrieve('__.OILS_RESULT');
163 $logger->debug( "script result is [$res]" );
164 return JSON->JSON2perl( $res );
168 my( $self, $path ) = @_;
170 if ($self->{_path}{$path}) {
171 $self->{_path}{$path} = 0;
173 return $self->{_path}{$path};
175 if ($_paths{$path}) {
178 return $_paths{$path};
183 my( $self, $path ) = @_;
185 if (!$self->{_path}{$path}) {
186 $self->{_path}{$path} = 1;
189 if (!$_paths{$path}) {
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);
207 my( $self, $file ) = @_;
209 push @{ $self->{libs} }, $file
210 if (! grep {$_ eq $file} @{ $self->{libs} });
212 if (!$self->{_loaded}{$file} && $self->run( $file )) {
213 $self->{_loaded}{$file} = 1;
215 return $self->{_loaded}{$file};
225 my( $self, $key ) = @_;
226 return $self->context->property_get($key);
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;
237 my( $self, $key, $val, $RO ) = @_;
238 return unless defined($key);
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(
254 sub { my( $k, $v ) = @_; $val = $v; } :
259 $self->context->property_by_path($key, "");
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);
276 for my $f ( $fm->properties ) {
279 $self->insert("$key.$f", $val);
281 $ctx->property_by_path(
285 my $k = _js_prop_name(shift());
291 my $k = _js_prop_name(shift());
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);
309 for my $k ( keys %$hash ) {
312 $self->insert("$key.$k", $v);
314 $ctx->property_by_path(
316 sub { $hash->{_js_prop_name(shift())} },
319 my( $hashkey, $val ) = @_;
320 $hash->{_js_prop_name($hashkey)} = $val;
332 my( $self, $key, $array ) = @_;
333 my $ctx = $self->context;
334 return undef unless ($ctx and $key and $array);
336 my $a = $ctx->array_by_path($key);
339 for my $v ( @$array ) {
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 );
346 $ctx->array_set_element( $a, $ind, $v ) if defined($v);
352 sub _xmlhttprequest_send {
357 my $blocking = shift;
358 my $headerlist = shift;
361 my $ctx = $self->context;
363 # just so perl has access to it...
364 $ctx->object_by_path('__xmlhttpreq_hash.id'.$id);
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);
374 my $ua = LWP::UserAgent->new;
375 $ua->agent("OpenILS/0.1");
377 my $req = HTTP::Request->new($method => $url => $headers => $data);
378 my $res = $ua->request($req);
380 if ($res->is_success) {
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);
391 our %_jsonopensrfrequest_cache = ();
393 sub _jsonopensrfrequest_connect {
398 my $ctx = $self->context;
399 $ctx->object_by_path('__jsonopensrfreq_hash.id'.$id);
401 my $ses = $_jsonopensrfrequest_cache{$id} ||
402 do { $_jsonopensrfrequest_cache{$id} = OpenSRF::AppSession->create($service) };
405 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.connected', 1);
407 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.connected', 0);
411 sub _jsonopensrfrequest_disconnect {
415 my $ctx = $self->context;
416 $ctx->object_by_path('__jsonopensrfreq_hash.id'.$id);
418 my $ses = $_jsonopensrfrequest_cache{$id};
424 sub _jsonopensrfrequest_finish {
428 my $ctx = $self->context;
429 $ctx->object_by_path('__jsonopensrfreq_hash.id'.$id);
431 my $ses = $_jsonopensrfrequest_cache{$id};
435 delete $_jsonopensrfrequest_cache{$id};
438 sub _jsonopensrfrequest_send {
443 my $blocking = shift;
446 my @p = @{ JSON->JSON2perl($params) };
448 my $ctx = $self->context;
450 # just so perl has access to it...
451 $ctx->object_by_path('__jsonopensrfreq_hash.id'.$id);
453 my $ses = $_jsonopensrfrequest_cache{$id} ||
454 do { $_jsonopensrfrequest_cache{$id} = OpenSRF::AppSession->create($service) };
455 my $req = $ses->request($method,@p);
459 my $res = $req->recv->content;
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');
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 );
477 sub _parse_xml_string {
486 $doc = XML::LibXML->new->parse_string( $string );
490 warn "Could not parse document: $e\n";
494 _JS_DOM($self->context, $key, $doc);
502 if ($node->nodeType == 9) {
503 $node = $node->documentElement;
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);
512 #warn("$key = DOMImplementation().createDocument($ns,'$n');");
513 $ctx->eval("$key = new DOMImplementation().createDocument($ns,'$n');");
515 $key = $key.'.documentElement';
518 for my $a ($node->attributes) {
519 my $n = $a->nodeName;
523 #warn("$key.setAttribute('$n','$v');");
524 $ctx->eval("$key.setAttribute('$n','$v');");
529 for my $c ($node->childNodes) {
530 if ($c->nodeType == 1) {
531 my $n = $c->nodeName;
532 my $ns = $node->namespaceURI;
535 $ns =~ s/'/\'/gso if ($ns);
536 $ns = "'$ns'" if ($ns);
537 $ns = 'null' unless ($ns);
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);
543 } elsif ($c->nodeType == 3) {
546 #warn("$key.appendChild($key.ownerDocument.createTextNode('$n'));");
547 #warn("path is $key.item($k);");
548 $ctx->eval("$key.appendChild($key.ownerDocument.createTextNode('$n'));");
550 } elsif ($c->nodeType == 4) {
553 #warn("$key.appendChild($key.ownerDocument.createCDATASection('$n'));");
554 $ctx->eval("$key.appendChild($key.ownerDocument.createCDATASection('$n'));");
556 } elsif ($c->nodeType == 8) {
559 #warn("$key.appendChild($key.ownerDocument.createComment('$n'));");
560 $ctx->eval("$key.appendChild($key.ownerDocument.createComment('$n'));");
563 warn "ACK! I don't know how to handle node type ".$c->nodeType;
578 // in case we run on an implimentation that doesn't have "undefined";
581 function Cast (obj, class_constructor) {
583 if (eval(class_constructor + '["_isfieldmapper"]')) {
584 obj = eval("new " + class_constructor + "(obj)");
593 function JSON2js (json) {
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")');
601 eval( 'obj = ' + json );
603 debug("Error building JSON object with string " + E + "\nString:\n" + json );
611 function object2Array(obj) {
612 if( obj == null ) return null;
614 var arr = new Array();
615 for( var i = 0; i < obj.length; i++ ) {
622 function js2JSON(arg) {
623 return _js2JSON(arg);
626 function _js2JSON(arg) {
629 switch (typeof arg) {
634 if (arg._isfieldmapper) { /* magi-c-ast for fieldmapper objects */
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;
643 if( typeof arg.a[i] != 'object' ) {
646 } else if( typeof arg.a[i] == 'object'
647 && arg.a[i]._isfieldmapper) {
652 arr[i] = object2Array(arg.a[i]);
658 return "/*--S " + arg.classname + " --*/" + js2JSON(arg.a) + "/*--E " + arg.classname + " --*/";
662 if (arg.constructor == Array) {
664 for (i = 0; i < arg.length; ++i) {
675 return '[' + o + ']';
677 } else if (typeof arg.toString != 'undefined') {
685 o += js2JSON(i) + ':' + v;
701 if( isNaN(arg) ) throw "JSON.js encountered NaN in js2JSON()";
710 return '"' + String(arg).replace(/(["\\])/g, '\\$1') + '"';