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($$);
160sub ReadDomTree ($)
161{
162    my ($filename) = @_;
163
164    my $root = {};
165    my $data = {
166        'current_node' => $root,
167        'node_stack' => []
168    };
169    my $parser = new XML::Parser(
170        'Handlers' => {
171            'Start' => sub {HandleStartTag($data, @_)},
172            'End' => sub{HandleEndTag($data, @_)},
173            'Char' => sub{HandleText($data, @_)}
174        });
175    $parser->parsefile($filename);
176
177#    PrintNode("", $root);
178
179    return $root;
180}
181
182
183
184
185sub PrintNode($$)
186{
187    my ($indentation, $node) = @_;
188
189    if (defined $node->{'__attributes__'})
190    {
191        while (my ($name,$attribute) = each(%{$node->{'__attributes__'}}))
192        {
193            printf("    %s%s -> %s\n", $indentation, $name, $attribute);
194        }
195    }
196
197    while (my ($key,$value) = each(%$node))
198    {
199        if ($key eq '__text__')
200        {
201            printf("%stext '%s'\n", $indentation, $value);
202        }
203        elsif ($key eq '__attributes__')
204        {
205            next;
206        }
207        elsif (ref($value) eq "ARRAY")
208        {
209            foreach my $item (@$value)
210            {
211                printf("%s%s {\n", $indentation, $key);
212                PrintNode($indentation."    ", $item);
213                printf("%s}\n", $indentation);
214            }
215        }
216        else
217        {
218            printf("%s%s {\n", $indentation, $key);
219            PrintNode($indentation."    ", $value);
220            printf("%s}\n", $indentation);
221        }
222    }
223}
224
225
226sub HandleStartTag ($$$@)
227{
228    my ($data, $expat, $element, @attributes) = @_;
229
230    # Create new node with attributes.
231    my $node = {'__attributes__' => {@attributes}};
232
233    # Append it to the list of $element objects.
234    my $current_node = $data->{'current_node'};
235    $current_node->{$element} = [] unless defined $current_node->{$element};
236    push @{$current_node->{$element}}, $node;
237
238    # Make the new node the current node.
239    push @{$data->{'node_stack'}}, $current_node;
240    $data->{'current_node'} = $node;
241}
242
243sub HandleEndTag ($$$)
244{
245    my ($data, $expat, $element) = @_;
246
247    # Restore the parent node as current node.
248    $data->{'current_node'} = pop @{$data->{'node_stack'}};
249}
250
251sub HandleText ($$$)
252{
253    my ($data, $expat, $text) = @_;
254    if ($text !~ /^\s*$/)
255    {
256        $data->{'current_node'}->{'__text__'} .= $text;
257    }
258}
259
260=head2 Read($self, $filename)
261
262    Read the releases.xml file as doctree and parse its content.
263
264=cut
265sub Read ($$)
266{
267    my ($self, $filename) = @_;
268
269    my $document = ReadDomTree($filename);
270    foreach my $release_node (@{$document->{'releases'}->[0]->{'release'}})
271    {
272        my $version_node = GetFirstChild($release_node, "version");
273        my $version_major = GetText(GetFirstChild($version_node, "major"));
274        my $version_minor = GetText(GetFirstChild($version_node, "minor"), "0");
275        my $version_micro = GetText(GetFirstChild($version_node, "micro"), "0");
276        my $version = sprintf("%d.%d.%d", $version_major, $version_minor, $version_micro);
277        die "could not read version from releases.xml" if $version eq "";
278
279        push @{$self->{'releases'}}, $version;
280
281        my $download_node = GetFirstChild($release_node, "downloads");
282        my $package_format = GetText(GetFirstChild($download_node, "package-format"));
283        my $url_template = GetText(GetFirstChild($download_node, "url-template"));
284        my $upgrade_code = GetText(GetFirstChild($download_node, "upgrade-code"));
285        my $build_id = GetText(GetFirstChild($download_node, "build-id"));
286        die "could not read package format from releases.xml" if $package_format eq "";
287
288        $self->{$version}->{$package_format}->{'upgrade-code'} = $upgrade_code;
289        $self->{$version}->{$package_format}->{'build-id'} = $build_id;
290
291        foreach my $item_node (@{$download_node->{'item'}})
292        {
293            my ($language, $download_data) = ParseDownloadData($item_node, $url_template);
294            if (defined $download_data && defined $language)
295            {
296                $self->{$version}->{$package_format}->{$language} = $download_data;
297            }
298        }
299    }
300}
301
302
303
304
305=head2 ParseDownloadData ($download_node)
306
307    Parse the data for one set of download data (there is one per release and package format).
308
309=cut
310sub ParseDownloadData ($$)
311{
312    my ($item_node, $url_template) = @_;
313
314    my $language = GetText(GetFirstChild($item_node, "language"));
315    my $checksum_node = GetFirstChild($item_node, "checksum");
316    if ( ! defined $checksum_node)
317    {
318        print STDERR "releases data file corrupt (item has no 'checksum' node)\n";
319        return undef;
320    }
321    my $checksum_type = GetAttribute($checksum_node, "type");
322    my $checksum_value = GetText($checksum_node);
323    my $file_size = GetText(GetFirstChild($item_node, "size"));
324    my $product_code = GetText(GetFirstChild($item_node, "product-code"));
325
326    my $url = $url_template;
327    $url =~ s/\%L/$language/g;
328    return (
329        $language,
330        {
331            'URL' => $url,
332            'checksum-type' => $checksum_type,
333            'checksum-value' => $checksum_value,
334            'file-size' => $file_size,
335            'product-code' => $product_code
336        });
337}
338
339
340
341
342=head2 GetPreviousVersion($version)
343
344    Look up $version in the sorted list of released versions.  Return
345    the previous element.  Whe $version is not found then return the
346    last element (under the assumption that $version will be the next
347    released version).
348
349=cut
350sub GetPreviousVersion ($)
351{
352    my ($current_version) = @_;
353
354    my $release_data = installer::patch::ReleasesList::Instance();
355    my $previous_version = undef;
356    foreach my $version (@{$release_data->{'releases'}})
357    {
358        if ($version eq $current_version)
359        {
360            return $previous_version;
361        }
362        else
363        {
364            $previous_version = $version;
365        }
366    }
367
368    return $previous_version;
369}
370
371
372
373
374
3751;
376