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 OpenSRF::Utils::JSON;
6 use JavaScript::SpiderMonkey;
9 use Time::HiRes qw/time/;
10 use vars qw/%_paths $__json_js/;
12 { local $/ = undef; $__json_js = <DATA>; }
16 $logger->info("script_runner: destroying self: $self");
21 $logger->info("script_runner: destroying context...");
22 $runner->context->destroy;
23 delete($$runner{$_}) for (keys %$runner);
29 $class = ref($class) || $class;
30 $params{paths} ||= [];
31 $params{reset_count} ||= 0;
33 my $self = bless { file => $params{file},
34 libs => $params{libs},
35 reset_count => $params{reset_count},
37 _path => {%_paths} } => $class;
39 $self->add_path($_) for @{$params{paths}};
44 my( $self, $context ) = @_;
45 $self->{ctx} = $context if $context;
51 $self->context( new JavaScript::SpiderMonkey );
52 $self->context->init();
56 # eating our own dog food with insert
57 $self->insert(log_stdout => sub { print "@_\n"; } );
58 $self->insert(log_stderr => sub { warn "@_\n"; } );
59 $self->insert(log_activity => sub { $logger->activity("script_runner: @_"); return 1;} );
60 $self->insert(log_error => sub { $logger->error("script_runner: @_"); return 1;} );
61 $self->insert(log_warn => sub { $logger->warn("script_runner: @_"); return 1;} );
62 $self->insert(log_info => sub { $logger->info("script_runner: @_"); return 1;} );
63 $self->insert(log_debug => sub { $logger->debug("script_runner: @_"); return 1;} );
64 $self->insert(log_internal => sub { $logger->internal("script_runner: @_"); return 1;} );
65 $self->insert(debug => sub { $logger->debug("script_runner: @_"); return 1;} );
66 $self->insert(alert => sub { $logger->warn("script_runner: @_"); return 1;} );
67 $self->insert(load_lib => sub { $self->load_lib(@_); return 1;});
69 # OpenSRF support functions
71 _OILS_FUNC_jsonopensrfrequest_send =>
72 sub { $self->_jsonopensrfrequest_send(@_); }
75 _OILS_FUNC_jsonopensrfrequest_connect =>
76 sub { $self->_jsonopensrfrequest_connect(@_); }
79 _OILS_FUNC_jsonopensrfrequest_disconnect =>
80 sub { $self->_jsonopensrfrequest_disconnect(@_); }
83 _OILS_FUNC_jsonopensrfrequest_finish =>
84 sub { $self->_jsonopensrfrequest_finish(@_); }
87 # XML support functions
89 _OILS_FUNC_xmlhttprequest_send =>
90 sub { $self->_xmlhttprequest_send(@_); }
93 _OILS_FUNC_xml_parse_string =>
94 sub { $self->_parse_xml_string(@_); }
97 while ( my $e = shift @{$self->{_env}} ) {
98 $self->insert( @$e{ qw/key value readonly/ } => 1 );
101 while ( my $e = shift @{$self->{_methods}} ) {
102 $self->insert_method( @$e{ qw/key name meth/ } => 1 );
105 $self->load_lib($_) for @{$self->{libs}};
110 sub refresh_context {
112 $logger->debug("Refreshing JavaScript Context...");
113 $self->context->destroy;
114 $logger->debug("Context destroyed");
115 $self->{_loaded} = {};
116 $logger->debug("Loaded scripts removed");
118 $logger->debug("New Context initialized");
123 my( $self, $filename ) = @_;
124 $self->{file} = $filename;
127 sub runs { shift()->{_runs} }
133 $self->{reset_count} = $count if ($count);
134 return $self->{reset_count};
144 $file = $self->{file};
147 $self->refresh_context
148 if ($self->reset_count && $self->runs > $self->reset_count);
150 $self->{_runs}++ if ($_real);
152 $file = $self->_find_file($file);
153 $logger->debug("full script file path: $file");
155 if( ! open(F, $file) ) {
156 $logger->error("Error opening script file: $file");
160 my $js = $self->context;
165 $self->insert('environment.result' => {});
168 #print ( "full script is [$content]" );
171 if( !$js || !$content || !$js->eval($content) ) {
172 $logger->error("$file Eval failed: $@");
175 $logger->debug("eval of $file took ". sprintf('%0.3f', time - $s) . " seconds");
178 $self->insert('__' => {'OILS_RESULT' => ''});
179 $js->eval($__json_js."__.OILS_RESULT = js2JSON(environment.result);");
180 $res = $self->retrieve('__.OILS_RESULT');
185 $logger->debug( "script result is [$res]" );
186 return OpenSRF::Utils::JSON->JSON2perl( $res );
190 my( $self, $path ) = @_;
192 if ($self->{_path}{$path}) {
193 $self->{_path}{$path} = 0;
195 return $self->{_path}{$path};
197 if ($_paths{$path}) {
200 return $_paths{$path};
205 my( $self, $path ) = @_;
207 if (!$self->{_path}{$path}) {
208 $self->{_path}{$path} = 1;
211 if (!$_paths{$path}) {
221 for my $p ( keys %{ $self->{_path} } ) {
222 next unless ($self->{_path}{$p});
223 my $full = join('/',$p,$file);
224 return $full if (-e $full);
229 my( $self, $file ) = @_;
231 my @paths = keys %{$self->{_path}};
232 $logger->debug("script_runner: Loading lib file $file : paths=[@paths]");
234 push @{ $self->{libs} }, $file
235 if (! grep {$_ eq $file} @{ $self->{libs} });
237 if (!$self->{_loaded}{$file}) {
239 $self->{_loaded}{$file} = 1;
241 return $self->{_loaded}{$file};
251 my( $self, $key ) = @_;
252 return $self->context->property_get($key);
256 my( $self, $obj_key, $meth_name, $sub, $stop) = @_;
258 push @{$self->{_methods}}, { key => $obj_key => name => $meth_name, meth => $sub } unless ($stop);
260 my $obj = $self->context->object_by_path( $obj_key );
261 $self->context->function_set( $meth_name, $sub, $obj ) if $obj;
266 my( $self, $key, $val, $RO, $stop ) = @_;
267 return unless defined($key);
269 push @{$self->{_env}}, { key => $key => value => $val, readonly => $RO } unless ($stop);
271 if (ref($val) =~ /^Fieldmapper/o) {
272 $self->insert_fm($key, $val, $RO);
273 } elsif (ref($val) and $val =~ /ARRAY/o) {
274 $self->insert_array($key, $val, $RO);
275 } elsif (ref($val) and $val =~ /HASH/o) {
276 $self->insert_hash($key, $val, $RO);
277 } elsif (ref($val) and $val =~ /CODE/o) {
278 $self->context->function_set( $key, $val );
279 } elsif (!ref($val)) {
280 if( defined($val) ) {
281 $self->context->property_by_path(
283 ( !$RO ? (sub { $val }, sub { my( $k, $v ) = @_; $val = $v; }) : () )
286 $self->context->property_by_path($key, "");
298 my( $self, $key, $fm, $RO ) = @_;
299 my $ctx = $self->context;
300 return undef unless ($ctx and $key and $fm);
301 my $o = $ctx->object_by_path($key);
303 for my $f ( $fm->properties ) {
306 $self->insert("$key.$f", $val);
308 $ctx->property_by_path(
313 my $k = _js_prop_name(shift());
317 my $k = _js_prop_name(shift());
330 my( $self, $key, $hash, $RO ) = @_;
331 my $ctx = $self->context;
332 return undef unless ($ctx and $key and $hash);
333 $ctx->object_by_path($key);
335 for my $k ( keys %$hash ) {
338 $self->insert("$key.$k", $v);
340 $ctx->property_by_path(
343 (sub { $hash->{_js_prop_name(shift())} },
345 my( $hashkey, $val ) = @_;
346 $hash->{_js_prop_name($hashkey)} = $val;
358 my( $self, $key, $array ) = @_;
359 my $ctx = $self->context;
360 return undef unless ($ctx and $key and $array);
362 my $a = $ctx->array_by_path($key);
365 for my $v ( @$array ) {
367 my $tmp_index = $__array_id++;
368 my $elobj = $ctx->object_by_path('__tmp_arr_el'.$tmp_index);
369 $self->insert('__tmp_arr_el'.$tmp_index, $v);
370 $ctx->array_set_element_as_object( $a, $ind, $elobj );
372 $ctx->array_set_element( $a, $ind, $v ) if defined($v);
378 sub _xmlhttprequest_send {
383 my $blocking = shift;
384 my $headerlist = shift;
387 my $ctx = $self->context;
389 # just so perl has access to it...
390 $ctx->object_by_path('__xmlhttpreq_hash.id'.$id);
392 my $headers = new HTTP::Headers;
393 my @lines = split(/\n/so, $headerlist);
394 for my $line (@lines) {
395 if ($line =~ /^(.+?)|(.+)$/o) {
396 $headers->header($1 => $2);
400 my $ua = LWP::UserAgent->new;
401 $ua->agent("OpenILS/0.1");
403 my $req = HTTP::Request->new($method => $url => $headers => $data);
404 my $res = $ua->request($req);
406 if ($res->is_success) {
408 $ctx->property_by_path('__xmlhttpreq_hash.id'.$id.'.responseText', $res->content);
409 $ctx->property_by_path('__xmlhttpreq_hash.id'.$id.'.readyState', 4);
410 $ctx->property_by_path('__xmlhttpreq_hash.id'.$id.'.statusText', $res->status_line);
411 $ctx->property_by_path('__xmlhttpreq_hash.id'.$id.'.status', $res->code);
417 our %_jsonopensrfrequest_cache = ();
419 sub _jsonopensrfrequest_connect {
424 my $ctx = $self->context;
425 $ctx->object_by_path('__jsonopensrfreq_hash.id'.$id);
427 my $ses = $_jsonopensrfrequest_cache{$id} ||
428 do { $_jsonopensrfrequest_cache{$id} = OpenSRF::AppSession->create($service) };
431 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.connected', 1);
433 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.connected', 0);
437 sub _jsonopensrfrequest_disconnect {
441 my $ctx = $self->context;
442 $ctx->object_by_path('__jsonopensrfreq_hash.id'.$id);
444 my $ses = $_jsonopensrfrequest_cache{$id};
450 sub _jsonopensrfrequest_finish {
454 my $ctx = $self->context;
455 $ctx->object_by_path('__jsonopensrfreq_hash.id'.$id);
457 my $ses = $_jsonopensrfrequest_cache{$id};
461 delete $_jsonopensrfrequest_cache{$id};
464 sub _jsonopensrfrequest_send {
469 my $blocking = shift;
472 my @p = @{ OpenSRF::Utils::JSON->JSON2perl($params) };
474 my $ctx = $self->context;
476 # just so perl has access to it...
477 $ctx->object_by_path('__jsonopensrfreq_hash.id'.$id);
479 my $ses = $_jsonopensrfrequest_cache{$id} ||
480 do { $_jsonopensrfrequest_cache{$id} = OpenSRF::AppSession->create($service) };
481 my $req = $ses->request($method,@p);
485 my $res = $req->recv->content;
487 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.responseText', OpenSRF::Utils::JSON->perl2JSON($res));
488 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.readyState', 4);
489 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.statusText', 'OK');
490 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.status', '200');
493 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.responseText', '');
494 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.readyState', 4);
495 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.statusText', $req->failed->status );
496 $ctx->property_by_path('__jsonopensrfreq_hash.id'.$id.'.status', $req->failed->statusCode );
503 sub _parse_xml_string {
512 $doc = XML::LibXML->new->parse_string( $string );
516 warn "Could not parse document: $e\n";
520 _JS_DOM($self->context, $key, $doc);
528 if ($node->nodeType == 9) {
529 $node = $node->documentElement;
531 my $n = $node->nodeName;
532 my $ns = $node->namespaceURI;
533 $ns =~ s/'/\'/gso if ($ns);
534 $ns = "'$ns'" if ($ns);
535 $ns = 'null' unless ($ns);
538 #warn("$key = DOMImplementation().createDocument($ns,'$n');");
539 $ctx->eval("$key = new DOMImplementation().createDocument($ns,'$n');");
541 $key = $key.'.documentElement';
544 for my $a ($node->attributes) {
545 my $n = $a->nodeName;
549 #warn("$key.setAttribute('$n','$v');");
550 $ctx->eval("$key.setAttribute('$n','$v');");
555 for my $c ($node->childNodes) {
556 if ($c->nodeType == 1) {
557 my $n = $c->nodeName;
558 my $ns = $node->namespaceURI;
561 $ns =~ s/'/\'/gso if ($ns);
562 $ns = "'$ns'" if ($ns);
563 $ns = 'null' unless ($ns);
565 #warn("$key.appendChild($key.ownerDocument.createElementNS($ns,'$n'));");
566 $ctx->eval("$key.appendChild($key.ownerDocument.createElementNS($ns,'$n'));");
567 _JS_DOM($ctx, "$key.childNodes.item($k)",$c);
569 } elsif ($c->nodeType == 3) {
572 #warn("$key.appendChild($key.ownerDocument.createTextNode('$n'));");
573 #warn("path is $key.item($k);");
574 $ctx->eval("$key.appendChild($key.ownerDocument.createTextNode('$n'));");
576 } elsif ($c->nodeType == 4) {
579 #warn("$key.appendChild($key.ownerDocument.createCDATASection('$n'));");
580 $ctx->eval("$key.appendChild($key.ownerDocument.createCDATASection('$n'));");
582 } elsif ($c->nodeType == 8) {
585 #warn("$key.appendChild($key.ownerDocument.createComment('$n'));");
586 $ctx->eval("$key.appendChild($key.ownerDocument.createComment('$n'));");
589 warn "ACK! I don't know how to handle node type ".$c->nodeType;
605 // in case we run on an implimentation that doesn't have "undefined";
608 function Cast (obj, class_constructor) {
610 if (eval(class_constructor + '["_isfieldmapper"]')) {
611 obj = eval("new " + class_constructor + "(obj)");
620 function JSON2js (json) {
622 json = String(json).replace( /\/\*--\s*S\w*?\s*?\s+\w+\s*--\*\//g, 'Cast(');
623 json = String(json).replace( /\/\*--\s*E\w*?\s*?\s+(\w+)\s*--\*\//g, ', "$1")');
628 eval( 'obj = ' + json );
630 debug("Error building JSON object with string " + E + "\nString:\n" + json );
638 function object2Array(obj) {
639 if( obj == null ) return null;
641 var arr = new Array();
642 for( var i = 0; i < obj.length; i++ ) {
649 function js2JSON(arg) {
650 return _js2JSON(arg);
653 function _js2JSON(arg) {
656 switch (typeof arg) {
661 if (arg._isfieldmapper) { /* magi-c-ast for fieldmapper objects */
663 if( arg.a.constructor != Array ) {
664 var arr = new Array();
665 for( var i = 0; i < arg.a.length; i++ ) {
666 if( arg.a[i] == null ) {
667 arr[i] = null; continue;
670 if( typeof arg.a[i] != 'object' ) {
673 } else if( typeof arg.a[i] == 'object'
674 && arg.a[i]._isfieldmapper) {
679 arr[i] = object2Array(arg.a[i]);
685 return "/*--S " + arg.classname + " --*/" + js2JSON(arg.a) + "/*--E " + arg.classname + " --*/";
689 if (arg.constructor == Array) {
691 for (i = 0; i < arg.length; ++i) {
702 return '[' + o + ']';
704 } else if (typeof arg.toString != 'undefined') {
712 o += js2JSON(i) + ':' + v;
728 if( isNaN(arg) ) throw "JSON.js encountered NaN in js2JSON()";
737 return '"' + String(arg).replace(/(["\\])/g, '\\$1') + '"';