1#************************************************************** 2# 3# Licensed to the Apache Software Foundation (ASF) under one 4# or more contributor license agreements. See the NOTICE file 5# distributed with this work for additional information 6# regarding copyright ownership. The ASF licenses this file 7# to you under the Apache License, Version 2.0 (the 8# "License"); you may not use this file except in compliance 9# with the License. You may obtain a copy of the License at 10# 11# http://www.apache.org/licenses/LICENSE-2.0 12# 13# Unless required by applicable law or agreed to in writing, 14# software distributed under the License is distributed on an 15# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16# KIND, either express or implied. See the License for the 17# specific language governing permissions and limitations 18# under the License. 19# 20#************************************************************** 21 22package installer::patch::ReleasesList; 23 24use XML::Parser; 25use File::Spec; 26 27use strict; 28 29=head1 NAME 30 31 package installer::patch::ReleasesList - Functions for accessing the instsetoo_native/data/releases.xml file 32 33=cut 34 35 36my $Instance = undef; 37 38=head2 Instance() 39 40 Return the singleton instance. 41 42=cut 43sub Instance() 44{ 45 if ( ! defined $Instance) 46 { 47 $Instance = new installer::patch::ReleasesList( 48 File::Spec->catfile($ENV{'SRC_ROOT'}, "instsetoo_native", "data", "releases.xml")); 49 } 50 return $Instance; 51} 52 53 54 55 56=head2 new($class, $filename) 57 58 Internal constructor. Don't call. 59 60=cut 61sub new ($$) 62{ 63 my ($class, $filename) = @_; 64 65 my $self = { 66 'releases' => [] 67 }; 68 bless($self, $class); 69 70 71 $self->Read($filename); 72 73 74 return $self; 75} 76 77 78 79 80=head2 GetFirstChild ($node, $child_name) 81 82 Internal function that returns the first child. Use only when the 83 first child is the (expected) only child in a list. 84 85=cut 86sub GetFirstChild ($$) 87{ 88 my ($node, $child_name) = @_; 89 90 if ( ! defined $node) 91 { 92 return undef; 93 } 94 else 95 { 96 my $value = $node->{$child_name}; 97 if (ref($value) eq 'ARRAY') 98 { 99 return $value->[0]; 100 } 101 else 102 { 103 return $value; 104 } 105 } 106} 107 108 109 110 111=head2 GetText ($node) 112 113 Internal function that returns the trimmed text content of a node. 114 115=cut 116sub GetText ($;$) 117{ 118 my ($node, $default_text) = @_; 119 120 if ( ! defined $node) 121 { 122 if (defined $default_text) 123 { 124 return $default_text; 125 } 126 else 127 { 128 return ""; 129 } 130 } 131 else 132 { 133 my $text = $node->{'__text__'}; 134 $text =~ s/(^\s+|\s+$)//g; 135 return $text; 136 } 137} 138 139 140 141sub GetAttribute ($$) 142{ 143 my ($node, $attribute_name) = @_; 144 145 my $attributes = $node->{'__attributes__'}; 146 if ( ! defined $attributes) 147 { 148 return undef; 149 } 150 else 151 { 152 return $attributes->{$attribute_name}; 153 } 154} 155 156 157 158 159sub PrintNode($$); 160 161=head2 ReadDomTree ($filename) 162 163 Read the dom tree for the XML in $filename. 164 165 Note that 166 a) this was initially written for another XML library that provided the dom tree directly. 167 b) the dom tree creation is basic and simple but good enough for the current format. 168 When the format should change substantially, then we may need a better parser. 169 170=cut 171sub ReadDomTree ($) 172{ 173 my ($filename) = @_; 174 175 my $root = {}; 176 my $data = { 177 'current_node' => $root, 178 'node_stack' => [] 179 }; 180 my $parser = new XML::Parser( 181 'Handlers' => { 182 'Start' => sub {HandleStartTag($data, @_)}, 183 'End' => sub{HandleEndTag($data, @_)}, 184 'Char' => sub{HandleText($data, @_)} 185 }); 186 $parser->parsefile($filename); 187 188# PrintNode("", $root); 189 190 return $root; 191} 192 193 194 195 196=head HandleStartTag ($data, $expat, $element_name, @attributes) 197 198 Callback for start tags. 199 200 A new hash is appended to the array that is referenced by the parent by $element_name. 201 That means that when this function ends there the new hash can be referenced by 202 my $parent = $data->{'node_stack'}->[-1]; 203 my $new_hash = $parent->{$element_name}->[-1]; 204 205 Note that, just like in other implementations of dom trees, 206 $parent->{$element_name} is an array, even when there is only one 207 element. 208 209 The new hash is empty or contains the given @attributes as hash. 210 When fully read (ie its end tag has been processed) then it can contain two special keys: 211 __attributes__ for the attributes 212 __text__ for the concatenated text parts 213 214=cut 215sub HandleStartTag ($$$@) 216{ 217 my ($data, $expat, $element_name, @attributes) = @_; 218 219 # Create new node with attributes. 220 my $node = {'__attributes__' => {@attributes}}; 221 222 # Append it to the list of $element_name objects. 223 my $current_node = $data->{'current_node'}; 224 $current_node->{$element_name} = [] unless defined $current_node->{$element_name}; 225 push @{$current_node->{$element_name}}, $node; 226 227 # Make the new node the current node. 228 push @{$data->{'node_stack'}}, $current_node; 229 $data->{'current_node'} = $node; 230} 231 232=head HandleEndTag ($data, $expat, $element_name, @attributes) 233 234 Callback for end tags. 235 236=cut 237sub HandleEndTag ($$$) 238{ 239 my ($data, $expat, $element) = @_; 240 241 # Restore the parent node as current node. 242 $data->{'current_node'} = pop @{$data->{'node_stack'}}; 243} 244 245=head2 HandleText ($data, $expat, $text) 246 247 Callback for text. 248 249 $text is appended to the __text__ member of the current node in 250 the dom tree. 251 252=cut 253sub HandleText ($$$) 254{ 255 my ($data, $expat, $text) = @_; 256 if ($text !~ /^\s*$/) 257 { 258 $data->{'current_node'}->{'__text__'} .= $text; 259 } 260} 261 262 263 264 265=head2 PrintNode ($indentation, $node) 266 267 For debugging. 268 Print $node recursively with initial $indentation. 269 270=cut 271sub PrintNode($$) 272{ 273 my ($indentation, $node) = @_; 274 275 if (defined $node->{'__attributes__'}) 276 { 277 while (my ($name,$attribute) = each(%{$node->{'__attributes__'}})) 278 { 279 printf(" %s%s -> %s\n", $indentation, $name, $attribute); 280 } 281 } 282 283 while (my ($key,$value) = each(%$node)) 284 { 285 if ($key eq '__text__') 286 { 287 printf("%stext '%s'\n", $indentation, $value); 288 } 289 elsif ($key eq '__attributes__') 290 { 291 next; 292 } 293 elsif (ref($value) eq "ARRAY") 294 { 295 foreach my $item (@$value) 296 { 297 printf("%s%s {\n", $indentation, $key); 298 PrintNode($indentation." ", $item); 299 printf("%s}\n", $indentation); 300 } 301 } 302 else 303 { 304 printf("%s%s {\n", $indentation, $key); 305 PrintNode($indentation." ", $value); 306 printf("%s}\n", $indentation); 307 } 308 } 309} 310 311 312 313 314=head2 Read($self, $filename) 315 316 Read the releases.xml file as doctree and parse its content. 317 318=cut 319sub Read ($$) 320{ 321 my ($self, $filename) = @_; 322 323 my $document = ReadDomTree($filename); 324 foreach my $release_node (@{$document->{'releases'}->[0]->{'release'}}) 325 { 326 my $version_node = GetFirstChild($release_node, "version"); 327 my $version_major = GetText(GetFirstChild($version_node, "major")); 328 my $version_minor = GetText(GetFirstChild($version_node, "minor"), "0"); 329 my $version_micro = GetText(GetFirstChild($version_node, "micro"), "0"); 330 my $version = sprintf("%d.%d.%d", $version_major, $version_minor, $version_micro); 331 die "could not read version from releases.xml" if $version eq ""; 332 333 push @{$self->{'releases'}}, $version; 334 335 my $download_node = GetFirstChild($release_node, "downloads"); 336 my $package_format = GetText(GetFirstChild($download_node, "package-format")); 337 my $url_template = GetText(GetFirstChild($download_node, "url-template")); 338 my $upgrade_code = GetText(GetFirstChild($download_node, "upgrade-code")); 339 my $build_id = GetText(GetFirstChild($download_node, "build-id")); 340 die "could not read package format from releases.xml" if $package_format eq ""; 341 342 $self->{$version}->{$package_format}->{'upgrade-code'} = $upgrade_code; 343 $self->{$version}->{$package_format}->{'build-id'} = $build_id; 344 $self->{$version}->{$package_format}->{'url-template'} = $url_template; 345 346 my @languages = (); 347 foreach my $item_node (@{$download_node->{'item'}}) 348 { 349 my ($language, $download_data) = ParseDownloadData($item_node, $url_template); 350 if (defined $download_data && defined $language) 351 { 352 push @languages, $language; 353 $self->{$version}->{$package_format}->{$language} = $download_data; 354 } 355 } 356 $self->{$version}->{$package_format}->{'languages'} = \@languages; 357 } 358} 359 360 361 362 363=head2 ParseDownloadData ($item_node, $url_template) 364 365 Parse the data for one set of download data (there is one per release and package format). 366 367=cut 368sub ParseDownloadData ($$) 369{ 370 my ($item_node, $url_template) = @_; 371 372 my $language = GetText(GetFirstChild($item_node, "language")); 373 my $checksum_node = GetFirstChild($item_node, "checksum"); 374 if ( ! defined $checksum_node) 375 { 376 print STDERR "releases data file corrupt (item has no 'checksum' node)\n"; 377 return undef; 378 } 379 my $checksum_type = GetAttribute($checksum_node, "type"); 380 my $checksum_value = GetText($checksum_node); 381 my $file_size = GetText(GetFirstChild($item_node, "size")); 382 my $product_code = GetText(GetFirstChild($item_node, "product-code")); 383 384 my $url = $url_template; 385 $url =~ s/\%L/$language/g; 386 return ( 387 $language, 388 { 389 'URL' => $url, 390 'checksum-type' => $checksum_type, 391 'checksum-value' => $checksum_value, 392 'file-size' => $file_size, 393 'product-code' => $product_code 394 }); 395} 396 397 398 399 400=head2 Write($self, $filename) 401 402 Write the content of the releases data to a file named $filename. 403 404=cut 405sub Write ($$) 406{ 407 my ($self, $filename) = @_; 408 409 open my $out, ">", $filename || die "can not write releases data to ".$filename; 410 $self->WriteHeader($out); 411 $self->WriteContent($out); 412 close $out; 413} 414 415 416 417 418=head2 WriteContent ($self, $out) 419 420 Write the content of the releases.xml list. 421 422=cut 423sub WriteContent ($$) 424{ 425 my ($self, $out) = @_; 426 427 print $out "<releases>\n"; 428 # Write the data sets for each releases with the same sort order as @{$self->{'releases'}} 429 foreach my $version (@{$self->{'releases'}}) 430 { 431 print $out " <release>\n"; 432 433 my @version_array = split(/\./, $version); 434 printf $out " <version>\n"; 435 printf $out " <major>%s</major>\n", $version_array[0]; 436 printf $out " <minor>%s</minor>\n", $version_array[1]; 437 printf $out " <micro>%s</micro>\n", $version_array[2]; 438 printf $out " </version>\n"; 439 440 # Write one download data set per package format. 441 while (my ($package_format, $data) = each %{$self->{$version}}) 442 { 443 print $out " <download>\n"; 444 printf $out " <package-format>%s</package-format>\n", $package_format; 445 print $out " <url-template>\n"; 446 printf $out " %s\n", $data->{'url-template'}; 447 print $out " </url-template>\n"; 448 printf $out " <upgrade-code>%s</upgrade-code>\n", $data->{'upgrade-code'}; 449 printf $out " <build-id>%s</build-id>\n", $data->{'build-id'}; 450 451 foreach my $language (@{$data->{'languages'}}) 452 { 453 my $language_data = $data->{$language}; 454 print $out " <item>\n"; 455 printf $out " <language>%s</language>\n", $language; 456 printf $out " <checksum type=\"%s\">%s</checksum>\n", 457 $language_data->{'checksum-type'}, 458 $language_data->{'checksum-value'}; 459 printf $out " <size>%s</size>\n", $language_data->{'file-size'}; 460 printf $out " <product-code>%s</product-code>\n", $language_data->{'product-code'}; 461 print $out " </item>\n"; 462 } 463 464 print $out " </download>\n"; 465 } 466 467 print $out " </release>\n"; 468 } 469 470 print $out "</releases>\n"; 471} 472 473 474 475 476=head2 WriteHeader ($self, $out) 477 478 Write the header for the releases.xml list. 479 480=cut 481sub WriteHeader ($$) 482{ 483 my ($self, $out) = @_; 484 485print $out <<EOT; 486<?xml version='1.0' encoding='UTF-8'?> 487<!--*********************************************************** 488 * 489 * Licensed to the Apache Software Foundation (ASF) under one 490 * or more contributor license agreements. See the NOTICE file 491 * distributed with this work for additional information 492 * regarding copyright ownership. The ASF licenses this file 493 * to you under the Apache License, Version 2.0 (the 494 * "License"); you may not use this file except in compliance 495 * with the License. You may obtain a copy of the License at 496 * 497 * http://www.apache.org/licenses/LICENSE-2.0 498 * 499 * Unless required by applicable law or agreed to in writing, 500 * software distributed under the License is distributed on an 501 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 502 * KIND, either express or implied. See the License for the 503 * specific language governing permissions and limitations 504 * under the License. 505 * 506 ***********************************************************--> 507EOT 508} 509 510 511 512 513=head2 GetPreviousVersion($version) 514 515 Look up $version in the sorted list of released versions. Return 516 the previous element. Whe $version is not found then return the 517 last element (under the assumption that $version will be the next 518 released version). 519 520=cut 521sub GetPreviousVersion ($) 522{ 523 my ($current_version) = @_; 524 525 my $release_data = installer::patch::ReleasesList::Instance(); 526 my $previous_version = undef; 527 foreach my $version (@{$release_data->{'releases'}}) 528 { 529 if ($version eq $current_version) 530 { 531 return $previous_version; 532 } 533 else 534 { 535 $previous_version = $version; 536 } 537 } 538 539 return $previous_version; 540} 541 542 543 544 545 5461; 547