File Coverage

blib/lib/ServiceNow/Simple.pm
Criterion Covered Total %
statement 22 24 91.6
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 30 32 93.7


line stmt bran cond sub pod time code
1             package ServiceNow::Simple;
2 1     1   26374 use strict;
  1         3  
  1         55  
3 1     1   6 use warnings FATAL => 'all';
  1         2  
  1         71  
4            
5             our $VERSION = '0.09';
6            
7 1     1   1260 use Data::Dumper;
  1         12564  
  1         87  
8 1     1   1095 use FindBin;
  1         1235  
  1         52  
9 1     1   1250 use HTTP::Cookies;
  1         26227  
  1         44  
10 1     1   7734 use HTTP::Request::Common;
  1         59491  
  1         118  
11 1     1   1533 use LWP::UserAgent;
  1         28955  
  1         44  
12 1     1   597 use SOAP::Lite;
  0            
  0            
13             use XML::Simple;
14             use Carp;
15            
16             $Data::Dumper::Indent=1;
17             $Data::Dumper::Sortkeys=1;
18            
19             our %config;
20             my $user;
21             my $pword;
22            
23             BEGIN
24             {
25             my $module = 'ServiceNow/Simple.pm';
26             my $cfg = $INC{$module};
27             unless ($cfg)
28             {
29             croak "Wrong case in use statement or $module module renamed. Perl is case sensitive!!!\n";
30             }
31             my $compiled = !(-e $cfg); # if the module was not read from disk => the script has been "compiled"
32             $cfg =~ s/\.pm$/.cfg/;
33             if ($compiled or -e $cfg)
34             {
35             # in a Perl2Exe or PerlApp created executable or PerlCtrl
36             # generated COM object or the cfg is known to exist
37             eval {require $cfg};
38             if ($@ and $@ !~ /Can't locate /) #' <-- syntax higlighter
39             {
40             carp "Error in $cfg : $@\n";
41             }
42             }
43             }
44            
45            
46             sub new
47             {
48             my $proto = shift;
49             my $class = ref($proto) || $proto;
50            
51             my $self = {};
52             bless($self, $class);
53            
54             # Run initialisation code
55             $self->_init(@_);
56            
57             return $self;
58             }
59            
60            
61             sub get
62             {
63             my ($self, $args_h) = @_;
64            
65             my $method = $self->_get_method('get');
66             my @params = $self->_load_args($args_h);
67             my $result = $self->{soap}->call($method => @params);
68            
69             # Print faults to log file or stderr
70             $self->_print_fault($result);
71            
72             if ($result && $result->body && $result->body->{'getResponse'})
73             {
74             return $result->body->{'getResponse'};
75             }
76            
77             return;
78             }
79            
80            
81             sub get_keys
82             {
83             my ($self, $args_h) = @_;
84            
85             my $method = $self->_get_method('getKeys');
86             my @params = $self->_load_args($args_h);
87             my $result = $self->{soap}->call($method => @params);
88            
89             # Print faults to log file or stderr
90             $self->_print_fault($result);
91            
92             my $data_hr;
93             if ($result && $result->body && $result->body->{'getKeysResponse'})
94             {
95             $data_hr = $result->body->{'getKeysResponse'};
96             }
97            
98             if ($self->print_results())
99             {
100             print Data::Dumper->Dump([$data_hr], ['data_hr']) . "\n";
101             }
102            
103             return $data_hr;
104             }
105            
106            
107             sub update
108             {
109             # Note, one of the query_pairs must contain sys_id
110             my ($self, $args_h) = @_;
111            
112             my $method = $self->_get_method('update');
113             my @params = $self->_load_args($args_h);
114             my $result = $self->{soap}->call($method => @params);
115            
116             # Print faults to log file or stderr
117             $self->_print_fault($result);
118            
119             my $sys_id;
120             if ($result && $result->body && $result->body->{updateResponse} && $result->body->{updateResponse}{sys_id})
121             {
122             $sys_id = $result->body->{updateResponse}{sys_id};
123             }
124            
125             if ($self->print_results())
126             {
127             print Data::Dumper->Dump([$sys_id], ['sys_id']) . "\n";
128             }
129            
130             return $sys_id;
131             }
132            
133            
134             sub get_records
135             {
136             my ($self, $args_h) = @_;
137            
138             my $method = $self->_get_method('getRecords');
139            
140             # Check if we need to limit the columns returned. Assume that IF there is a
141             # __exclude_columns defined, it will over-ride this (and hence speed up the
142             # process). So __exclude_columns => undef is just a speed up, even though
143             # there may be a LOT more data returned.
144             my $the_columns;
145             if ($args_h->{__columns} && ! exists($args_h->{__exclude_columns}))
146             {
147             $the_columns = $args_h->{__columns};
148             delete $args_h->{__columns};
149            
150             if ($self->{instance_url} && $self->{__columns}{$self->{instance_url}}{$self->{table}}{$the_columns})
151             {
152             # We already have a list of fields to exclude for this form and __columns list
153             $args_h->{__exclude_columns} = $self->{__columns}{$self->{instance_url}}{$self->{table}}{$the_columns};
154             }
155             elsif ($self->{instance} && $self->{__columns}{$self->{instance}}{$self->{table}}{$the_columns})
156             {
157             # We already have a list of fields to exclude for this form and __columns list
158             $args_h->{__exclude_columns} = $self->{__columns}{$self->{instance}}{$self->{table}}{$the_columns};
159             }
160             else
161             {
162             # Do we have the list of fields for this form
163             my @to_include = split /,/, $the_columns;
164             my %all_fields;
165             if ($self->{instance_url} && $self->{__columns}{__list}{$self->{instance_url}}{$self->{table}})
166             {
167             my $list = $self->{__columns}{__list}{$self->{instance_url}}{$self->{table}};
168             %all_fields = map { $_ => 1 } split /,/, $list;
169             }
170             elsif ($self->{instance} && $self->{__columns}{__list}{$self->{instance}}{$self->{table}})
171             {
172             my $list = $self->{__columns}{__list}{$self->{instance}}{$self->{table}};
173             %all_fields = map { $_ => 1 } split /,/, $list;
174             }
175             else
176             {
177             # Might as well query based on arguments passed, so remove what we don't want in query
178             delete $args_h->{__exclude_columns};
179            
180             # Just get one record, to minimise data transfered
181             my $original_limit = $args_h->{__limit};
182             $args_h->{__limit} = 1;
183            
184             my @p = $self->_load_args($args_h);
185             my $r = $self->{soap}->call($method => @p);
186            
187             # Add back in the limit if there was one, otherwise remove
188             if ($original_limit)
189             {
190             $args_h->{__limit} = $original_limit;
191             }
192             else
193             {
194             delete $args_h->{__limit};
195             }
196            
197             # GG handle no result!
198             %all_fields = map { $_ => 1 } keys %{$r->body->{getRecordsResponse}{getRecordsResult}};
199            
200             # Store the full list in case we need it later
201             if ($self->{instance_url})
202             {
203             $self->{__columns}{__list}{$self->{instance_url}}{$self->{table}} = join(',', keys %all_fields);
204             }
205             else
206             {
207             $self->{__columns}{__list}{$self->{instance}}{$self->{table}} = join(',', keys %all_fields);
208             }
209             }
210             my @to_exclude;
211             # Remove from the all fields hash those fields we want in the results
212             # excluding the special meaning fields, those starting with '__'
213             foreach my $ti (grep { $_ !~ /^__/} @to_include)
214             {
215             delete $all_fields{$ti};
216             }
217            
218             # Add into the args we will pass
219             $args_h->{__exclude_columns} = join(',', sort keys %all_fields);
220            
221             # Store for later use
222             if ($self->{instance_url})
223             {
224             $self->{__columns}{$self->{instance_url}}{$self->{table}}{$the_columns} = $args_h->{__exclude_columns};
225             }
226             else
227             {
228             $self->{__columns}{$self->{instance}}{$self->{table}}{$the_columns} = $args_h->{__exclude_columns};
229             }
230             }
231             }
232            
233             #__max_records => 1000,
234             #__chunks => 200,
235             #__callback => 'write_csv',
236            
237             # If this is a __callback call we need to set __first_row, __last_row and iterate over the
238             # possible result set. Assume chunk size of 250 (default) unless __chunks is set. Limit
239             # the max number of records if __max_records is set. Assume __first_row is 0 unless set.
240             # Ignore __last_row and base on __chunks||250
241            
242             if ($args_h->{__callback})
243             {
244             # Store the callback function and remove it from the arguments hash
245             my $callback = $args_h->{__callback};
246             delete $args_h->{__callback};
247            
248             # Get the chunk size and clean up the arguments hash
249             my $chunk_size = 250;
250             if ($args_h->{__chunks})
251             {
252             $chunk_size = $args_h->{__chunks};
253             delete $args_h->{__chunks};
254             }
255            
256             my $max_records;
257             if ($args_h->{__max_records})
258             {
259             $max_records = $args_h->{__max_records};
260             delete $args_h->{__max_records};
261            
262             # max records takes precedence over chunk size
263             if ($chunk_size > $max_records)
264             {
265             $chunk_size = $max_records;
266             }
267             }
268            
269             $args_h->{__first_row} = 0 unless $args_h->{__first_row};
270             $args_h->{__last_row} = $chunk_size;
271            
272             # Remove other things not relavent or useful :)
273             delete $args_h->{__limits} if $args_h->{__limits};
274            
275             # If __columns, create an array of column names to pass
276             my @columns;
277             if ($the_columns)
278             {
279             @columns = split /,/, $the_columns;
280             }
281            
282             # Make the first chunked call to ServiceNow
283             my $total_records = 0;
284             my @params = $self->_load_args($args_h);
285             my $result = $self->{soap}->call($method => @params);
286            
287             # And now to the callback function
288             my ($data_hr, $count) = $self->_getRecordsResponse($result);
289             $total_records += $count;
290             { $callback->($data_hr, 1, \@columns); }
291            
292            
293             # Now chunk away...
294             while ($count == $chunk_size && (!defined($max_records) || $total_records < $max_records))
295             {
296             # Sleep if we need to
297             sleep($self->{__callback_sleep}) if $self->{__callback_sleep};
298            
299             # We need to get more
300             $args_h->{__first_row} += $chunk_size;
301             if (defined $max_records)
302             {
303             $args_h->{__last_row} = (($args_h->{__first_row} + $chunk_size) < $max_records) ? $args_h->{__first_row} + $chunk_size : $max_records;
304             }
305             else
306             {
307             $args_h->{__last_row} = $args_h->{__first_row} + $chunk_size;
308             }
309            
310             @params = $self->_load_args($args_h);
311             $result = $self->{soap}->call($method => @params);
312             ($data_hr, $count) = $self->_getRecordsResponse($result);
313             $total_records += $count;
314             { $callback->($data_hr, 0, \@columns); }
315             }
316             return { count => $total_records, rows => undef }; # data handled by the callback function
317             }
318             else
319             {
320             my @params = $self->_load_args($args_h);
321             my $result = $self->{soap}->call($method => @params);
322             my ($data_hr, $count) = $self->_getRecordsResponse($result);
323            
324             return $data_hr;
325             }
326             }
327            
328            
329             sub _getRecordsResponse
330             {
331             my ($self, $result) = @_;
332            
333             my $count = 0;
334             my $data_hr;
335             if ($result && $result->body && $result->body->{getRecordsResponse} && $result->body->{getRecordsResponse}{getRecordsResult})
336             {
337             my $data = $result->body->{getRecordsResponse}{getRecordsResult};
338             if (ref($data) eq 'HASH')
339             {
340             # There was only one record. For consistant return, convert to array of hash
341             $data_hr = { count => 1, rows => [ $data ] };
342             $count = 1;
343             }
344             else
345             {
346             $count = scalar(@$data);
347             $data_hr = { count => $count, rows => $data };
348             }
349             }
350            
351             # Print faults to log file or stderr
352             $self->_print_fault($result);
353            
354             if ($self->print_results() && $data_hr)
355             {
356             print Data::Dumper->Dump([$data_hr], ['data_hr']) . "\n";
357             }
358            
359             return ($data_hr, $count);
360             }
361            
362            
363             sub insert
364             {
365             my ($self, $args_h) = @_;
366            
367             # Results:
368             # --------
369             # Regular tables:
370             # The sys_id field and the display value of the target table are returned.
371             # Import set tables:
372             # The sys_id of the import set row, the name of the transformed target table (table),
373             # the display_name for the transformed target table, the display_value of the
374             # transformed target row, and a status field, which can contain inserted, updated,
375             # or error. There can be an optional status_message field or an error_message field
376             # value when status=error. When an insert did not cause a target row to be
377             # transformed, e.g. skipped because a key value is not specified, the sys_id field
378             # will contain the sys_id of the import set row rather than the targeted transform table.
379             # Import set tables with multiple transforms:
380             # The response from this type of insert will contain multiple sets of fields from the
381             # regular import set table insert wrapped in a multiInsertResponse parent element.
382             # Each set will contain a map field, showing which transform map created the response.
383            
384             my $method = $self->_get_method('insert');
385             my @params = $self->_load_args($args_h);
386             my $result = $self->{soap}->call($method => @params);
387            
388             # Print faults to log file or stderr
389             $self->_print_fault($result);
390            
391             # insertResponse
392             if ($result && $result->body && $result->body->{insertResponse})
393             {
394             return $result->body->{insertResponse};
395             }
396            
397             return;
398             }
399            
400            
401            
402             sub soap_debug
403             {
404             SOAP::Lite->import(+trace => 'all');
405             }
406            
407             sub print_results
408             {
409             my ($self, $flag) = @_;
410            
411             if (defined $flag)
412             {
413             $self->{__print_results} = $flag;
414             }
415             return $self->{__print_results};
416             }
417            
418            
419             sub SOAP::Transport::HTTP::Client::get_basic_credentials
420             {
421             return $user => $pword;
422             }
423            
424            
425             sub set_table
426             {
427             my ($self, $table) = @_;
428            
429             $self->{table} = $table;
430             $self->set_soap() if ($self->{instance} || $self->{instance_url});
431             if(!$self->{wsdl} || ($self->{wsdl}{$self->{instance}} && !$self->{wsdl}{$self->{instance}}{$table}) || ($self->{wsdl}{$self->{instance_url}} && !$self->{wsdl}{$self->{instance_url}}{$table}))
432             {
433             $self->_load_wsdl($table);
434             }
435             }
436            
437            
438             sub set_instance
439             {
440             my ($self, $instance) = @_;
441            
442             if ($self->{instance_url})
443             {
444             carp "instance_url is defined and will take precedence over a defined 'instance'\n";
445             }
446            
447             $self->{instance} = $instance;
448             $self->set_soap() if ($self->{table});
449             }
450            
451            
452             sub set_instance_url
453             {
454             my ($self, $instance_url) = @_;
455            
456             if ($self->{instance})
457             {
458             carp "instance is defined, instance_url will take precedence over a defined 'instance'\n";
459             }
460            
461             $self->{instance_url} = $instance_url;
462             $self->set_soap() if ($self->{table});
463             }
464            
465            
466             sub set_soap
467             {
468             my $self = shift;
469            
470             my $url;
471             if ($self->{instance_url})
472             {
473             if ($self->{instance_url} !~ m{/$})
474             {
475             $self->{instance_url} .= '/';
476             }
477             $url = $self->{instance_url} . $self->{table} . '.do?SOAP';
478             }
479             else
480             {
481             $url = 'https://' . $self->{instance} . '.service-now.com/' . $self->{table} . '.do?SOAP';
482             }
483            
484             # Do we need to show the display value for a reference field rather than the sys_id, or both
485             if ($self->{__display_value} && !$self->{__plus_display_value})
486             {
487             $url .= '&displayvalue=true';
488             }
489             elsif ($self->{__plus_display_value})
490             {
491             $url .= '&displayvalue=all';
492             }
493            
494             my %args = ( cookie_jar => HTTP::Cookies->new(ignore_discard => 1) ) ;
495             if ($self->{proxy})
496             {
497             $args{proxy} = [ https => $self->{proxy}, http => $self->{proxy} ];
498             }
499            
500             $self->{soap} = SOAP::Lite->proxy($url, %args);
501             }
502            
503            
504             sub _get_method
505             {
506             my ($self, $method) = @_;
507            
508             $self->{method} = $method;
509             $self->{__fault} = undef; # Clear any previous faults
510             return SOAP::Data->name($method)->attr({xmlns => 'http://www.service-now.com/'});
511             }
512            
513            
514             sub _load_args
515             {
516             my ($self, $args_h) = @_;
517             my (@args, $k, $v);
518            
519             my $fld_details;
520             if ($self->{instance_url})
521             {
522             $fld_details = $self->{wsdl}{$self->{instance_url}}{$self->{table}}{$self->{method}};
523             }
524             else
525             {
526             $fld_details = $self->{wsdl}{$self->{instance}}{$self->{table}}{$self->{method}};
527             }
528            
529             while (($k, $v) = each %$args_h)
530             {
531             if ($fld_details->{$k})
532             {
533             (my $type = $fld_details->{$k}{type}) =~ s/xsd://gms;
534             push @args, SOAP::Data->name( $k => $v )->type($type);
535             }
536             else
537             {
538             push @args, SOAP::Data->name( $k => $v );
539             }
540             }
541            
542             # Add in the limits if not in the arguments and defined in object
543             if (! $args_h->{__limit} && $self->{__limit})
544             {
545             push @args, SOAP::Data->name( __limit => $self->{__limit} );
546             }
547            
548             return @args;
549             }
550            
551            
552             sub _print_fault
553             {
554             my ($self, $result) = @_;
555            
556             if ($result->fault)
557             {
558             no warnings qw(uninitialized);
559             if ($self->{__log})
560             {
561             $self->{__log}->exp('E930 - faultcode =' . $result->fault->{faultcode} . "\n",
562             'faultstring =' . $result->fault->{faultstring} . "\n",
563             'detail =' . $result->fault->{detail} . "\n");
564             }
565             else
566             {
567             carp 'faultcode =' . $result->fault->{faultcode} . "\n" .
568             'faultstring =' . $result->fault->{faultstring} . "\n" .
569             'detail =' . $result->fault->{detail} . "\n";
570             }
571            
572             # Store the fault so it can be queried before the next ws call
573             # Cleared in _get_method()
574             $self->{__fault}{faultcode} = $result->fault->{faultcode};
575             $self->{__fault}{faultstring} = $result->fault->{faultstring};
576             $self->{__fault}{detail} = $result->fault->{detail};
577             }
578             }
579            
580            
581             sub _load_wsdl
582             {
583             my ($self, $table) = @_;
584            
585             my $ua = LWP::UserAgent->new();
586            
587             if ($self->{instance_url})
588             {
589             # Not sure if this is correct for instance_url case, since I have not been able to test it?
590             # expect instance_url format to be https://instance_url/
591             if ($self->{instance_url} =~ m{https?://([^/]+)/?$})
592             {
593             $ua->credentials($1 . ':443', 'Service-now', $user, $pword);
594             }
595             else
596             {
597             # Not in the format we expected...
598             carp '_load_wsdl instance_url format expected as https://instance_url/ but was ' . $self->{instance_url} . ", skipping WDSL load.\n";
599             return;
600             }
601             }
602             else
603             {
604             $ua->credentials($self->{instance} . '.service-now.com:443', 'Service-now', $user, $pword);
605             }
606            
607             # Note: There is no advantage in including the displayvalue=1 or displayvalue=all in the WSDL request
608             # in the case of displayvalue=all, the dv_ fields can not be excluded for fields you do request
609             # (or more accurately, fields you don't exclude)
610             my $response;
611             if ($self->{instance_url})
612             {
613             $response = $ua->get($self->{instance_url} . $table . '.do?WSDL');
614             }
615             else
616             {
617             $response = $ua->get('https://' . $self->{instance} . '.service-now.com/' . $table . '.do?WSDL');
618             }
619             if ($response->is_success())
620             {
621             #my $wsdl = XMLin($response->content, ForceArray => 1);
622             #$XML::Simple::PREFERRED_PARSER = '';
623             my $wsdl = XMLin($response->content);
624            
625             foreach my $method (grep { $_ !~ /Response$/ } keys %{ $wsdl->{'wsdl:types'}{'xsd:schema'}{'xsd:element'} })
626             {
627             #print "Method=$method\n";
628             my $e = $wsdl->{'wsdl:types'}{'xsd:schema'}{'xsd:element'}{$method}{'xsd:complexType'}{'xsd:sequence'}{'xsd:element'};
629             foreach my $fld (keys %{ $e })
630             {
631             #print "\n\n", join("\n", keys %{ $wsdl->{'wsdl:types'}{'xsd:schema'}{'xsd:element'}{deleteMultiple}{'xsd:complexType'}{'xsd:sequence'}{'xsd:element'} }), "\n";
632             #print " $fld => $e->{$fld}{type}\n";
633             if ($self->{instance_url})
634             {
635             $self->{wsdl}{$self->{instance_url}}{$table}{$method}{$fld} = $e->{$fld};
636             }
637             else
638             {
639             $self->{wsdl}{$self->{instance}}{$table}{$method}{$fld} = $e->{$fld};
640             }
641             }
642             }
643             }
644             }
645            
646            
647             sub set_display_value
648             {
649             my ($self, $flag) = @_;
650            
651             $self->{__display_value} = $flag;
652             $self->set_soap();
653             }
654            
655            
656             sub set_plus_display_value
657             {
658             my ($self, $flag) = @_;
659            
660             $self->{__plus_display_value} = $flag;
661             $self->set_soap();
662             $self->_load_wsdl($self->{table});
663             }
664            
665            
666             sub _init
667             {
668             my ($self, $args) = @_;
669            
670             # Did we have any of the persistant variables passed
671             my $k = '5Jv@sI9^bl@D*j5H3@:7g4H[2]d%Ks314aNuGeX;';
672             if ($args->{user})
673             {
674             $self->{persistant}{user} = $args->{user};
675             }
676             else
677             {
678             if (defined $config{user})
679             {
680             my $s = pack('H*', $config{user});
681             my $x = substr($k, 0, length($s));
682             my $u = $s ^ $x;
683             $self->{persistant}{user} = $u;
684             }
685             else
686             {
687             carp "No user defined, quitting\n";
688             exit(1);
689             }
690             }
691            
692             if ($args->{password})
693             {
694             $self->{persistant}{password} = $args->{password};
695             }
696             else
697             {
698             if (defined $config{password})
699             {
700             my $s = pack('H*', $config{password});
701             my $x = substr($k, 0, length($s));
702             my $u = $s ^ $x;
703             $self->{persistant}{password} = $u;
704             }
705             else
706             {
707             carp "No password defined, quitting\n";
708             exit(2);
709             }
710             }
711             if ($args->{proxy})
712             {
713             $self->{persistant}{proxy} = $args->{proxy};
714             }
715             elsif (defined $config{proxy})
716             {
717             $self->{persistant}{proxy} = $config{proxy};
718             }
719             $user = $self->{persistant}{user};
720             $pword = $self->{persistant}{password};
721            
722             # Stop environment variable from playing around with SOAP::Lite
723             if ($args->{__remove_env_proxy})
724             {
725             delete $ENV{HTTP_proxy} if $ENV{HTTP_proxy};
726             delete $ENV{HTTPS_proxy} if $ENV{HTTPS_proxy};
727             }
728             elsif ($self->{persistant}{proxy} && !$ENV{HTTPS_proxy})
729             {
730             $ENV{HTTPS_proxy} = $self->{persistant}{proxy};
731             }
732            
733            
734             # Handle the other passed arguments
735             $self->{__display_value} = $args->{__display_value} ? 1 : 0;
736             $self->{__plus_display_value} = $args->{__plus_display_value} ? 1 : 0;
737             $self->set_instance($args->{instance}) if $args->{instance};
738             $self->set_instance_url($args->{instance_url}) if $args->{instance_url};
739             $self->set_table($args->{table}) if $args->{table}; # Important this is after instance
740             $self->{__limit} = $args->{__limit} if $args->{__limit};
741             $self->{__log} = $args->{__log} if $args->{__log};
742             $self->{__print_results} = $args->{__print_results} if $args->{__print_results}; # Print results to stdout
743             $self->soap_debug() if $args->{__soap_debug};
744             if ($args->{table} && ($args->{instance} || $args->{instance_url}))
745             {
746             $self->set_soap();
747             }
748             }
749            
750             #####################################################################
751             # DO NOT REMOVE THE FOLLOWING LINE, IT IS NEEDED TO LOAD THIS LIBRARY
752             1;
753            
754            
755             __END__