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