File Coverage

blib/lib/perfSONAR_PS/Common.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package perfSONAR_PS::Common;
2              
3             =head1 NAME
4              
5             perfSONAR_PS::Common - A module that provides common methods for performing simple, necessary actions
6             within the perfSONAR-PS framework.
7              
8             =head1 DESCRIPTION
9              
10             This module is a catch all for common methods (for now) in the perfSONAR-PS framework. As such there
11             is no 'common thread' that each method shares. This module IS NOT an object, and the methods
12             can be invoked directly (and sparingly).
13              
14             =head1 DETAILS
15              
16             The API for this module aims to be simple; note that this is not an object and
17             each method does not have the 'self knowledge' of variables that may travel
18             between functions.
19              
20             =head1 API
21              
22             The API of perfSONAR_PS::Common offers simple calls to common activities in the
23             perfSONAR-PS framework.
24             =cut
25              
26 1     1   4 use strict;
  1         3  
  1         28  
27 1     1   5 use warnings;
  1         1  
  1         22  
28 1     1   5 use Exporter;
  1         2  
  1         35  
29 1     1   772 use IO::File;
  1         10472  
  1         124  
30 1     1   6 use Time::HiRes qw( gettimeofday );
  1         2  
  1         10  
31 1     1   178 use Log::Log4perl qw(get_logger :nowarn);
  1         2  
  1         9  
32 1     1   1487 use XML::LibXML;
  0            
  0            
33              
34             our $VERSION = 0.09;
35             use base 'Exporter';
36             our @EXPORT = ('readXML','defaultMergeMetadata',
37             'countRefs', 'genuid', 'extract', 'reMap', 'consultArchive',
38             'find', 'findvalue', 'escapeString', 'unescapeString',
39             'makeEnvelope', 'mapNamespaces', 'mergeConfig', 'mergeNodes_general');
40              
41              
42             =head2 find($node, $query, $return_first)
43             This function replicates the libxml "find" function. However, it formats
44             the query to work around some oddities in the find implementation. It
45             converts the xpath query to get rid of direct references like /nmwg:element
46             and replaces them with /*[name()='nmwg:element"] which avoids spurious
47             'undefined namespace' errors. It also wraps the find in an eval and returns
48             'undef' if ->find throws an errors. If the $return_first is set to one, the
49             function returns only the first node from the nodes found.
50             =cut
51             sub find {
52             my ($node, $query, $return_first) = @_;
53             my $logger = get_logger("perfSONAR_PS::Common");
54             my $res;
55              
56             $logger->debug("Query(pre-process): $query");
57             $query =~ s/\/([a-zA-Z_][a-zA-Z0-9\.\-\_]+:[a-zA-Z_][a-zA-Z0-9\.\-\_]+)\[/\/*[name()='$1' and /g;
58             $query =~ s/\/([a-zA-Z_][a-zA-Z0-9\.\-\_]+:[a-zA-Z_][a-zA-Z0-9\.\-\_]+)/\/*[name()='$1']/g;
59             $query =~ s/^([a-zA-Z_][a-zA-Z0-9\.\-\_]+:[a-zA-Z_][a-zA-Z0-9\.\-\_]+)\[/*[name()='$1' and /g;
60             $query =~ s/^([a-zA-Z_][a-zA-Z0-9\.\-\_]+:[a-zA-Z_][a-zA-Z0-9\.\-\_]+)/*[name()='$1']/g;
61             $logger->debug("Query(post-process): $query");
62              
63             eval {
64             $res = $node->find($query);
65             };
66             if ($@) {
67             $logger->error("Error finding value($query): $@");
68             return;
69             }
70              
71             if (defined $return_first and $return_first == 1) {
72             return $res->get_node(1);
73             }
74              
75             return $res;
76             }
77              
78             =head2 findvalue($node, $query)
79             This function is analogous to the libxml "findvalue" function. However, it
80             makes use of the 'find' function documented above. Unlike the libxml
81             findvalue function, this function will only return the text contents of the
82             first node found.
83             =cut
84             sub findvalue {
85             my ($node, $xpath) = @_;
86              
87             my $found_node;
88              
89             $found_node = find($node, $xpath, 1);
90              
91             return if (not defined $found_node);
92              
93             return $found_node->textContent;
94             }
95              
96             =head2 makeEnvelope($content)
97             Wraps the specified content in a soap envelope and returns it as a string.
98             =cut
99             sub makeEnvelope {
100             my($content) = @_;
101             my $logger = get_logger("perfSONAR_PS::Common");
102             my $string = "
103             $string .= " xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n";
104             $string .= " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n";
105             $string .= " xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n";
106             $string .= " \n";
107             $string .= " \n";
108             $string .= $content;
109             $string .= " \n";
110             $string .= "\n";
111             return $string;
112             }
113              
114             =head2 readXML($file)
115             Reads the file specified in '$file' and returns the XML contents in string
116             form. The tag will be extracted from the final returned string.
117             Function will warn on error, and return an empty string.
118             =cut
119             sub readXML {
120             my ($file) = @_;
121             my $logger = get_logger("perfSONAR_PS::Common");
122              
123             if(defined $file and $file ne "") {
124             my $XML = new IO::File("<".$file);
125             if(defined $XML) {
126             my $xmlstring = "";
127             while (<$XML>) {
128             if(!($_ =~ m/^<\?xml.*/)) {
129             $xmlstring .= $_;
130             }
131             }
132             $XML->close();
133             return $xmlstring;
134             } else {
135             $logger->error("Cannot open file \"".$file."\".");
136             }
137             } else {
138             $logger->error("Missing argument.");
139             }
140              
141             return "";
142             }
143              
144             =head2 chainMetadata($dom)
145             Given a dom of objects, this function will continuously loop through
146             performing a 'chaining' operation to share values between metadata objects.
147             An example would be:
148              
149            
150            
151            
152             128.4.133.167
153             stout
154            
155            
156             http://ggf.org/ns/nmwg/tools/snmp/2.0
157            
158              
159            
160            
161            
162             eth1
163             3
164             in
165            
166            
167             http://ggf.org/ns/nmwg/tools/snmp/2.0
168            
169              
170             Which would then become:
171              
172            
173            
174            
175             128.4.133.167
176             stout
177             eth1
178             3
179             in
180            
181            
182             http://ggf.org/ns/nmwg/tools/snmp/2.0/
183            
184              
185             This chaining is useful for 'factoring out' large chunks of XML.
186             =cut
187              
188             sub chainMetadata {
189             my($dom) = @_;
190             my $logger = get_logger("perfSONAR_PS::Common");
191              
192              
193             if(defined $dom and $dom ne "") {
194             my %mdChains = ();
195              
196             my $changes = 1;
197             while($changes) {
198             $changes = 0;
199             foreach my $md ($dom->getElementsByTagNameNS("http://ggf.org/ns/nmwg/base/2.0/", "metadata")) {
200             if($md->getAttribute("metadataIdRef")) {
201             if(!$mdChains{$md->getAttribute("metadataIdRef")}) {
202             $mdChains{$md->getAttribute("metadataIdRef")} = 0;
203             }
204             if($mdChains{$md->getAttribute("id")} != $mdChains{$md->getAttribute("metadataIdRef")}+1) {
205             $mdChains{$md->getAttribute("id")} = $mdChains{$md->getAttribute("metadataIdRef")}+1;
206             $changes = 1;
207             }
208             }
209             }
210             }
211              
212             my @sorted = sort {$mdChains{$a} <=> $mdChains{$b}} keys %mdChains;
213             for(my $x = 0; $x <= $#sorted; $x++) {
214             $mdChains{$sorted[$x]} = 0;
215             foreach my $md ($dom->getElementsByTagNameNS("http://ggf.org/ns/nmwg/base/2.0/", "metadata")) {
216             if($md->getAttribute("id") eq $sorted[$x]){
217             foreach my $md2 ($dom->getElementsByTagNameNS("http://ggf.org/ns/nmwg/base/2.0/", "metadata")) {
218             if($md->getAttribute("metadataIdRef") and
219             $md2->getAttribute("id") eq $md->getAttribute("metadataIdRef")){
220             defaultMergeMetadata($md2, $md);
221             $md->removeAttribute("metadataIdRef");
222             last;
223             }
224             }
225             last;
226             }
227             }
228             }
229             } else {
230             $logger->error("Missing argument.");
231             }
232              
233             return $dom;
234             }
235              
236             =head2 defaultMergeMetadata ($parent, $child)
237             This function will try to merge the specified parent metadata into the
238             child metadata. It will do this by first merging their subjects, then copying
239             all the eventTypes from the parent into the child and then merging all the
240             parameters blocks from the parent into the child.
241             =cut
242             sub defaultMergeMetadata {
243             my ($parent, $child, $eventTypeEquivalenceHandler) = @_;
244             my $logger = get_logger("perfSONAR_PS::Topology::Common");
245              
246             $logger->debug("Merging ".$parent->getAttribute("id")." with ".$child->getAttribute("id"));
247              
248             # verify that it's not a 'key' value
249             if (defined find($parent, "./*[local-name()='key' and namespace-uri()='http://ggf.org/ns/nmwg/base/2.0/']", 1)) {
250             throw perfSONAR_PS::Error_compat("error.common.merge", "Merging with a key metadata is invalid");
251             }
252              
253             if (defined find($child, "./*[local-name()='key' and namespace-uri()='http://ggf.org/ns/nmwg/base/2.0/']", 1)) {
254             throw perfSONAR_PS::Error_compat("error.common.merge", "Merging with a key metadata is invalid");
255             }
256              
257             # verify that the subject elements are the same namespace
258             my $parent_subjects = find($parent, "./*[local-name()='subject']", 0);
259             if ($parent_subjects->size() > 1) {
260             throw perfSONAR_PS::Error_compat("error.common.merge", "Metadata ".$parent->getAttribute("id")." has multiple subjects");
261             }
262             my $parent_subject = find($parent, "./*[local-name()='subject']", 1);
263              
264             my $child_subjects = find($child, "./*[local-name()='subject']", 0);
265             if ($child_subjects->size() > 1) {
266             throw perfSONAR_PS::Error_compat("error.common.merge", "Metadata ".$child->getAttribute("id")." has multiple subjects");
267             }
268             my $child_subject = find($child, "./*[local-name()='subject']", 1);
269              
270             if (not defined $child_subject and not defined $parent_subject) {
271             $logger->debug("No subject in parent or child: ".$child->toString);
272             }
273              
274             if (defined $child_subject and defined $parent_subject) {
275             if ($child_subject->namespaceURI ne $parent_subject->namespaceURI) {
276             throw perfSONAR_PS::Error_compat("error.common.merge", "Metadata ".$child->getAttribute("id")." and ".$parent->getAttribute("id")." have subjects with different namespaces.");
277             }
278              
279             # Merge the subjects
280             defaultMergeSubject($parent_subject, $child_subject);
281             } elsif (defined $parent_subject) {
282             # if the parent has a subject, but not the child, simply copy the subject from the parent
283             $child->addChild($parent_subject->cloneNode(1));
284             }
285              
286             # Copy over the event types
287             my %parent_eventTypes = ();
288             my %child_eventTypes = ();
289              
290             foreach my $ev ($parent->getChildrenByTagNameNS("http://ggf.org/ns/nmwg/base/2.0/", "eventType")) {
291             my $eventType = $ev->textContent;
292             $eventType =~ s/^\s+//;
293             $eventType =~ s/\s+$//;
294             $parent_eventTypes{$eventType} = $ev;
295             $logger->debug("Found eventType $eventType in child");
296             }
297              
298             foreach my $ev ($child->getChildrenByTagNameNS("http://ggf.org/ns/nmwg/base/2.0/", "eventType")) {
299             my $eventType = $ev->textContent;
300             $eventType =~ s/^\s+//;
301             $eventType =~ s/\s+$//;
302             $child_eventTypes{$eventType} = $ev;
303             $logger->debug("Found eventType $eventType in child");
304             }
305              
306             if (defined $eventTypeEquivalenceHandler) {
307             my @parent_evs = keys %parent_eventTypes;
308             my @child_evs = keys %child_eventTypes;
309              
310             my $common_evs = $eventTypeEquivalenceHandler->matchEventTypes(\@parent_evs, \@child_evs);
311              
312             foreach my $ev (keys %child_eventTypes) {
313             my $old_ev = $child->removeChild($child_eventTypes{$ev});
314             $child_eventTypes{$ev} = $old_ev;
315             }
316              
317             foreach my $ev (@{ $common_evs }) {
318             if (not defined $child_eventTypes{$ev}) {
319             $child->addChild($parent_eventTypes{$ev}->cloneNode(1));
320             } else {
321             $child->addChild($child_eventTypes{$ev});
322             }
323             }
324             } else {
325             if (scalar(keys %parent_eventTypes) > 0 or scalar(keys %child_eventTypes) > 0) {
326             # if we have a child metadata with nothing in it and a parent with
327             # something in it, copy all the parent's over.
328             if (scalar(keys %child_eventTypes) == 0) {
329             foreach my $ev (keys %parent_eventTypes) {
330             $child->addChild($parent_eventTypes{$ev}->cloneNode(1));
331             }
332             }
333             # both the child and the parent have eventTypes so only save the ones in common
334             elsif (scalar(keys %parent_eventTypes) > 0) {
335             my $in_common = 0;
336              
337             foreach my $ev (keys %child_eventTypes) {
338             if (not defined $parent_eventTypes{$ev}) {
339             $child->removeChild($child_eventTypes{$ev});
340             } else {
341             $in_common = 1;
342             }
343             }
344              
345             if (not $in_common) {
346             throw perfSONAR_PS::Error_compat("error.common.merge", "Metadata ".$child->getAttribute("id")." and ".$parent->getAttribute("id")." have no eventTypes in common");
347             }
348             }
349             }
350             }
351              
352             # Copy over any parameter blocks
353             my %params = ();
354             foreach my $params_elm ($child->getChildrenByTagNameNS("*", "parameters")) {
355             $params{$params_elm->namespaceURI} = $params_elm;
356             }
357              
358             foreach my $params_elm ($parent->getChildrenByTagNameNS("*", "parameters")) {
359             if (defined $params{$params_elm->namespaceURI}) {
360             defaultMergeParameters($params_elm, $params{$params_elm->namespaceURI});
361             } else {
362             $child->addChild($params_elm->cloneNode(1));
363             }
364             }
365              
366             return;
367             }
368              
369             =head2 defaultMergeParameters($parent, $child)
370             This function simply does a simple merge of the parent and child subject
371             element. If an element exists in the parent and not the child, it will be
372             added. If an element exists in both, an attempt will be made to merge them.
373             The only special case elements are parameter elements where the 'name'
374             attribute is checked to verify the equivalence. In all other case, the
375             elements name and namespace will be compared and if they're the same, the
376             function assumes that the child's should supercede the parent's.
377             =cut
378             sub defaultMergeSubject {
379             my ($subject_parent, $subject_child) = @_;
380             my $logger = get_logger("perfSONAR_PS::Topology::Common");
381              
382             my %comparison_attrs = (
383             parameter => ( name => '' ),
384             );
385              
386             my $new_subj = mergeNodes_general($subject_parent, $subject_child);
387              
388             $subject_child->replaceNode($new_subj);
389              
390             return;
391             }
392              
393             =head2 defaultMergeParameters($parent, $child)
394             This function takes parent and child parameter blocks and adds each
395             parameter element from the parent into the child. If a parameter by the
396             same name and namespace already exists in the child, the function will
397             merge the two parameters. In the case of a parameter with only a 'value'
398             attribute and no body, the child's will simply replace the parent. In the
399             case that elements exist as children below the parameter, the parameters
400             will be merged.
401             =cut
402             sub defaultMergeParameters {
403             my ($params_parent, $params_child) = @_;
404             my $logger = get_logger("perfSONAR_PS::Topology::Common");
405              
406             my %params = ();
407              
408             # look up all the parameters in the parent block
409             foreach my $param ($params_parent->getChildrenByTagNameNS("*", "parameter")) {
410             my $name = $param->getAttribute("name");
411             my $ns = $param->namespaceURI;
412              
413             $logger->debug("Found parameter $name in namespace $ns in parent");
414              
415             if (not $name) {
416             throw perfSONAR_PS::Error_compat("error.common.merge", "Attempting to merge a parameter with a missing 'name' attribute");
417             }
418              
419             $params{$ns} = () if (not defined $params{$ns});
420             $params{$ns}->{$name} = $param;
421             }
422              
423             # go through the set of parameters in the child block, merging parameter
424             # elements if they exist in both the child and the parent
425             foreach my $param ($params_child->getChildrenByTagNameNS("*", "parameter")) {
426             my $name = $param->getAttribute("name");
427             my $ns = $param->namespaceURI;
428              
429             $logger->debug("Found parameter $name in namespace $ns in child");
430              
431             if (not $name) {
432             throw perfSONAR_PS::Error_compat("error.common.merge", "Attempting to merge a parameter with a missing 'name' attribute");
433             }
434              
435             if (defined $params{$ns}->{$name}) {
436             $logger->debug("Merging parameter $name in namespace $ns with parameter in parent");
437              
438             $params{$ns} = () if (not defined $params{$ns});
439             my $new_param = mergeNodes_general($params{$ns}{$name}, $param);
440             $param->replaceNode($new_param);
441             delete $params{$ns}->{$name};
442             }
443             }
444              
445             # add any parameters that exist in the parent and not in the child
446             foreach my $ns (keys %params) {
447             foreach my $name (keys %{ $params{$ns} }) {
448             $params_child->addChild($params{$ns}->{$name}->cloneNode(1));
449             }
450             }
451              
452             return;
453             }
454              
455             =head2 mergeNodes_general($old_node, $new_node, $attrs)
456              
457             Takes two LibXML nodes containing structures and merges them together.
458             The $attrs variable is a pointer to a hash describing which attributes
459             on an element node should be compared to define equality. If an element's
460             name is not defined in the hash, the element is simply replaced if one of
461             the same name and namespace is found.
462              
463             To have links compared based on their 'id' attribute, you would specify $attrs as such:
464              
465             my %attrs = (
466             link => ( id => '' );
467             );
468             =cut
469             sub mergeNodes_general {
470             my ($old_node, $new_node, $comparison_attrs) = @_;
471             my $logger = get_logger("perfSONAR_PS::Topology::Common");
472              
473             if ($old_node->getType != $new_node->getType) {
474             $logger->warn("Inconsistent node types, old ".$old_node->getType. " vs new ".$new_node->getType . ", simply replacing old with new");
475             return $new_node->cloneNode(1);
476             }
477              
478             if ($new_node->getType == 3) { # text node
479             return $new_node->cloneNode(1);
480             }
481              
482             if ($new_node->getType != 1) {
483             $logger->warn("Received unknown node type: ".$new_node->getType.", returning new node");
484             return $new_node->cloneNode(1);
485             }
486              
487             if ($new_node->localname ne $old_node->localname) {
488             $logger->warn("Received inconsistent node names: ".$old_node->localname." and ".$new_node->getType.", returning new node");
489             return $new_node;
490             }
491              
492             my $ret_node = $old_node->cloneNode(1);
493              
494             my @new_attributes = $new_node->getAttributes();
495              
496             foreach my $attribute (@new_attributes) {
497             if ($attribute->getType == 2) {
498             $ret_node->setAttribute($attribute->getName, $attribute->getValue);
499             } else {
500             $logger->warn("Unknown attribute type, ".$attribute->getType.", skipping");
501             }
502             }
503              
504             my %elements = ();
505              
506             foreach my $elem ($ret_node->getChildNodes) {
507             next if (!defined $elem->localname);
508             $elements{$elem->localname} = () if (!defined $elements{$elem->localname});
509             push @{ $elements{$elem->localname} }, $elem;
510             }
511              
512             foreach my $elem ($new_node->getChildNodes) {
513             my $is_equal;
514              
515             if ($elem->getType == 3) {
516             # Since we don't know which text node it is, we have to
517             # remove all of them... sigh...
518             foreach my $tn ($ret_node->getChildNodes) {
519             if ($tn->getType == 3) {
520             $ret_node->removeChild($tn);
521             }
522             }
523              
524             $ret_node->addChild($elem->cloneNode(1));
525             }
526              
527             next if (!defined $elem->localname);
528              
529             my $old_elem;
530             if (defined $comparison_attrs->{$elem->localname} and defined $elements{$elem->localname}) {
531             my $i = 0;
532              
533             foreach my $tmp_elem (@{ $elements{$elem->localname} }) {
534              
535             # skip elements from different namespaces
536             next if ($elem->namespaceURI ne $tmp_elem->namespaceURI);
537              
538             $is_equal = 1;
539              
540             $logger->debug("Comparison attributes: ".Dumper($comparison_attrs->{$elem->localname}));
541              
542             if (not defined $comparison_attrs->{$elem->localname}->{'*'}) {
543             foreach my $attr (keys %{ $comparison_attrs->{$elem->localname} }) {
544             my $old_attr = $tmp_elem->getAttributes($attr);
545             my $new_attr = $elem->getAttributes($attr);
546              
547             if (defined $old_attr and defined $new_attr) {
548             # if the attribute exists in both the old node and the new node, compare them
549             if ($old_attr->getValue ne $new_attr->getValue) {
550             $is_equal = 0;
551             }
552             } elsif (defined $old_attr or defined $new_attr) {
553             # if the attribute exists in one or the other, obviously they cannot be equal
554             $is_equal = 0;
555             }
556             }
557             }
558              
559             if ($is_equal) {
560             $old_elem = $tmp_elem;
561             splice(@{ $elements{$elem->localname} }, $i, 1);
562             last;
563             }
564              
565             $i++;
566             }
567             } elsif (defined $elements{$elem->localname}) {
568             $old_elem = pop(@{ $elements{$elem->localname} });
569             }
570              
571             my $new_child;
572              
573             if (defined $old_elem) {
574             $new_child = mergeNodes_general($old_elem, $elem, $comparison_attrs);
575             $ret_node->removeChild($old_elem);
576             } else {
577             $new_child = $elem->cloneNode(1);
578             }
579              
580             $ret_node->appendChild($new_child);
581             }
582              
583             $logger->debug("Merged Node: ".$ret_node->toString);
584              
585             return $ret_node;
586             }
587              
588             =head2 countRefs($id, $dom, $uri, $element, $attr)
589             Given a ID, and a series of 'struct' objects and a key 'value' to search on, this function
590             will return a 'count' of the number of times the id was seen as a reference
591             to the objects. This is useful for eliminating 'dead' blocks that may not
592             contain a trigger. The function will return -1 on error.
593             =cut
594             sub countRefs {
595             my($id, $dom, $uri, $element, $attr) = @_;
596             my $logger = get_logger("perfSONAR_PS::Common");
597              
598             if((defined $id and $id ne "") and
599             (defined $dom and $dom ne "") and
600             (defined $uri and $uri ne "") and
601             (defined $element and $element ne "") and
602             (defined $attr and $attr ne "")) {
603             my $flag = 0;
604             foreach my $d ($dom->getElementsByTagNameNS($uri, $element)) {
605             if($id eq $d->getAttribute($attr)) {
606             $flag++;
607             }
608             }
609             return $flag;
610             } else {
611             $logger->error("Missing argument(s).");
612             }
613             $logger->debug("0 Refernces Found");
614             return -1;
615             }
616              
617             =head2 genuid()
618             Generates a random number.
619             =cut
620             sub genuid {
621             my $r = int(rand(16777216))+1048576;
622             return $r;
623             }
624              
625             =head2 extract($node)
626             Returns a 'value' from a xml element, either the value attribute or the
627             text field.
628             =cut
629             sub extract {
630             my($node, $clean) = @_;
631             my $logger = get_logger("perfSONAR_PS::Common");
632             if(defined $node and $node ne "") {
633             if($node->getAttribute("value")) {
634             return $node->getAttribute("value");
635             } else {
636             my $value = $node->textContent;
637             if($clean) {
638             $value =~ s/\s*//g;
639             }
640             if($value) {
641             return $value;
642             }
643             }
644             }
645             return "";
646             }
647              
648             =head2 mapNamespaces($node, \%namespaces)
649             Fills in a uri -> prefix mapping of the namespaces.
650             =cut
651             sub mapNamespaces {
652             my ($node, $namespaces) = @_;
653             my $logger = get_logger("perfSONAR_PS::Common");
654              
655             my $uri = $node->namespaceURI();
656             my $prefix = $node->prefix();
657             if(defined $prefix and $prefix ne "" and $uri) {
658             if(not defined $namespaces->{$uri}) {
659             $namespaces->{$uri} = $prefix;
660             $node->ownerDocument->getDocumentElement->setNamespace($uri, $prefix, 0);
661             }
662             } elsif ((not defined $prefix or $prefix eq "") and defined $uri) {
663             if (defined $namespaces->{$uri}) {
664             $node->setNamespace($uri, $namespaces->{$uri}, 1);
665             }
666             }
667             if($node->hasChildNodes()) {
668             foreach my $c ($node->childNodes) {
669             if($node->nodeType != 3) {
670             mapNamespaces($c, $namespaces);
671             }
672             }
673             }
674              
675             return;
676             }
677              
678             =head2 reMap(\%{$rns}, \%{$ns}, $dom_node)
679             Re-map the nodes namespace prefixes to known prefixes (to not screw with
680             the XPath statements that will occur later).
681             =cut
682             sub reMap {
683             my($requestNamespaces, $namespaces, $node, $set_owner_prefix) = @_;
684             my $logger = get_logger("perfSONAR_PS::Common");
685              
686             if($node->prefix and $node->namespaceURI()) {
687             if(!$requestNamespaces->{$node->namespaceURI()}) {
688             $requestNamespaces->{$node->namespaceURI()} = $node->prefix;
689             if ($set_owner_prefix) {
690             $node->ownerDocument->getDocumentElement->setNamespace($node->namespaceURI(), $node->prefix, 0);
691             }
692             $logger->debug("Setting namespace \"".$node->namespaceURI()."\" with prefix \"".$node->prefix."\".");
693             }
694             if(!($namespaces->{$node->prefix})) {
695             foreach my $ns (keys %{$namespaces}) {
696             if($namespaces->{$ns} eq $node->namespaceURI()) {
697             $node->setNamespace($namespaces->{$ns}, $ns, 1);
698             if ($set_owner_prefix) {
699             $node->ownerDocument->getDocumentElement->setNamespace($namespaces->{$ns}, $ns, 0);
700             }
701             $logger->debug("Re-mapping namespace \"".$namespaces->{$ns}."\" to prefix \"".$ns."\".");
702             last;
703             }
704             }
705             }
706             } elsif ($node->namespaceURI()) {
707             if (defined $requestNamespaces->{$node->namespaceURI()}) {
708             $logger->debug("Setting namespace \"".$node->namespaceURI()."\" with prefix \"".$requestNamespaces->{$node->namespaceURI()}."\".");
709             $node->setNamespace($node->namespaceURI(), $requestNamespaces->{$node->namespaceURI()}, 1);
710             } else {
711             my $new_prefix;
712             foreach my $ns (keys %{$namespaces}) {
713             if($namespaces->{$ns} eq $node->namespaceURI()) {
714             $new_prefix = $ns;
715             last;
716             }
717             }
718              
719             if (not $new_prefix) {
720             $logger->debug("No prefix for namespace ".$node->namespaceURI().": generating one");
721             do {
722             $new_prefix = "pref".(genuid()%1000);
723             } while (defined $namespaces->{$new_prefix});
724             }
725              
726             $node->setNamespace($node->namespaceURI(), $new_prefix, 1);
727             if ($set_owner_prefix) {
728             $node->ownerDocument->getDocumentElement->setNamespace($node->namespaceURI(), $new_prefix, 0);
729             }
730             $logger->debug("Re-mapping namespace \"".$node->namespaceURI()."\" to prefix \"".$new_prefix."\".");
731             $requestNamespaces->{$node->namespaceURI()} = $new_prefix;
732             }
733             }
734             if($node->hasChildNodes()) {
735             foreach my $c ($node->childNodes) {
736             if($node->nodeType != 3) {
737             $requestNamespaces = reMap($requestNamespaces, $namespaces, $c, $set_owner_prefix);
738             }
739             }
740             }
741             return $requestNamespaces;
742             }
743              
744             =head2 consultArchive($host, $port, $endpoint, $request)
745             This function can be used to easily consult a measurement archive. It's a
746             thin wrapper around the sendReceive function in the perfSONAR_PS::Transport
747             module. You specify the host, port and endpoint for the MA you wish to
748             consult and the request you wish to send. The function sends the request to
749             the MA, parses the response and returns to you the LibXML element
750             corresponding to the nmwg:message portion of the response. The return value
751             an array of the form ($status, $res) where status is 0 means the function
752             was able to send the request and get a properly formed response and -1 on
753             failure. $res contains the LibXML element on success and an error message
754             on failure.
755             =cut
756             sub consultArchive {
757             my ($host, $port, $endpoint, $request) = @_;
758             my $logger = get_logger("perfSONAR_PS::Common");
759              
760             # start a transport agent
761             my $sender = new perfSONAR_PS::Transport($host, $port, $endpoint);
762              
763             my $envelope = makeEnvelope($request);
764             my $error;
765             my $start_time = time;
766             my $response = $sender->sendReceive($envelope, "", \$error);
767             my $end_time = time;
768              
769             $logger->debug("Time to make request: ".($end_time - $start_time));
770              
771             if ($error ne "") {
772             my $msg = "Error while sending request to server: $error";
773             $logger->error($msg);
774             return (-1, $msg);
775             }
776              
777             if (not defined $response or $response eq "") {
778             my $msg = "No response received from status service";
779             $logger->error($msg);
780             return (-1, $msg);
781             }
782              
783             my $doc;
784             eval {
785             my $parser = XML::LibXML->new();
786             $doc = $parser->parse_string($response);
787             };
788             if ($@) {
789             my $msg = "Couldn't parse response: $@";
790             $logger->error($msg);
791             return (-1, $msg);
792             }
793              
794             my $nodeset = find($doc, "//nmwg:message", 0);
795             if($nodeset->size <= 0) {
796             my $msg = "Message element not found in response";
797             $logger->error($msg);
798             return (-1, $msg);
799             } elsif($nodeset->size > 1) {
800             my $msg = "Too many message elements found in response";
801             $logger->error($msg);
802             return (-1, $msg);
803             }
804              
805             my $nmwg_msg = $nodeset->get_node(1);
806              
807             return (0, $nmwg_msg);
808             }
809              
810             =head2 escapeString($string)
811             This function does some basic XML character escaping. Replacing < with
812             <, & with &, etc.
813             =cut
814             sub escapeString {
815             my ($input) = @_;
816              
817             $input =~ s/&/&/g;
818             $input =~ s/
819             $input =~ s/>/>/g;
820             $input =~ s/'/'/g;
821             $input =~ s/"/"/g;
822              
823             return $input;
824             }
825              
826             =head2 unescapeString($string)
827             This function does some basic XML character escaping. Replacing < with
828             <, & with &, etc.
829             =cut
830             sub unescapeString {
831             my ($input) = @_;
832              
833             $input =~ s/</
834             $input =~ s/>/>/g;
835             $input =~ s/'/'/g;
836             $input =~ s/"/"/g;
837             $input =~ s/&/&/g;
838              
839             return $input;
840             }
841              
842             =head2 mergeConfig($base, $specific)
843             Merges the configurations in $base and $specific.
844             =cut
845             sub mergeConfig {
846             my ($base, $specific) = @_;
847             my $logger = get_logger("perfSONAR_PS::Common");
848              
849             my %elements = (
850             port => 1,
851             endpoint => 1 );
852              
853             my $ret_config = mergeHash($base, $specific, \%elements);
854              
855             return $ret_config;
856             }
857              
858             # mergeHash($base, $specific, $skip_elements)
859             # Internal function that merges $base and $specific into a unified hash. The
860             # elements from the $specific hash will be used whenever a collision occurs.
861             # $skip_elements is a hash containing the set of keys whose values should be
862             # ignored.
863             sub mergeHash {
864             my ($base, $specific, $skip_elements) = @_;
865             my $logger = get_logger("perfSONAR_PS::Common");
866              
867             my $new = duplicateHash($base, $skip_elements);
868              
869             foreach my $key (keys %{ $specific }) {
870             if (defined $skip_elements->{$key}) {
871             next;
872             }
873              
874             if (ref $specific->{$key} eq "HASH") {
875             if (not defined $new->{$key}) {
876             $new->{$key} = duplicateHash($specific->{$key}, $skip_elements);
877             } else {
878             $new->{$key} = mergeHash($new->{$key}, $specific->{$key}, $skip_elements);
879             }
880             } else {
881             $new->{$key} = $specific->{$key};
882             }
883             }
884              
885             return $new;
886             }
887              
888             # duplicateArray($array, $skip_elements)
889             # Internal function that duplicates the specified hash. It ignores hash
890             # elements with the keys specified in the $skip_elements.
891             sub duplicateHash {
892             my ($hash, $skip_elements) = @_;
893             my $logger = get_logger("perfSONAR_PS::Common");
894              
895             my %new = ();
896              
897             foreach my $key (keys %{ $hash }) {
898             if (defined $skip_elements->{$key}) {
899             next;
900             }
901              
902             if (ref $hash->{$key} eq "HASH") {
903             $new{$key} = duplicateHash($hash->{$key}, $skip_elements);
904             } elsif (ref $hash->{$key} eq "ARRAY") {
905             $new{$key} = duplicateArray($hash->{$key}, $skip_elements);
906             } else {
907             $new{$key} = $hash->{$key};
908             }
909             }
910              
911             return \%new;
912             }
913              
914             # duplicateArray($array, $skip_elements)
915             # Internal function that duplicates the specified array. When duplicating
916             # hash elements in the array, the elements specified in skip_elements will be
917             # skipped.
918             sub duplicateArray {
919             my ($array, $skip_elements) = @_;
920              
921             my @old_array = @{ $array };
922             my @new = ();
923             for(my $i = 0; $i <= $#old_array; $i++) {
924             if (ref $old_array[$i] eq "ARRAY") {
925             $new[$i] = duplicateArray($old_array[$i], $skip_elements);
926             } elsif (ref $old_array[$i] eq "HASH") {
927             $new[$i] = duplicateHash($old_array[$i], $skip_elements);
928             } else {
929             $new[$i] = $old_array[$i];
930             }
931             }
932              
933             return \@new;
934             }
935              
936             =head2 convertISO($iso)
937             Given the time in ISO format, conver to 'unix' epoch seconds.
938             =cut
939             sub convertISO {
940             my($iso) = @_;
941             my $logger = get_logger("perfSONAR_PS::Common");
942             if(defined $iso and $iso ne "") {
943             my ($date_portion, $time_portion) = split(/T/, $iso);
944             my($year, $mon, $day) = split(/-/, $date_portion);
945             my($hour, $min, $sec) = split(/:/, $time_portion);
946             my $frac = "";
947             ($sec, $frac) = split(/\./, $sec);
948             my $zone = $frac;
949             $frac =~ s/\D+//g;
950             $zone =~ s/\d+//g;
951             if($zone eq "Z") {
952             return timegm($sec,$min,$hour,$day,$mon-1,$year-1900);
953             }
954             else {
955             return timelocal($sec,$min,$hour,$day,$mon-1,$year-1900);
956             }
957             } else {
958             $logger->error("Missing argument.");
959             return "N/A";
960             }
961             }
962              
963             1;
964              
965              
966             __END__