jmx4perl/lib/JMX/Jmx4Perl/Agent/Jolokia/Meta.pm

380 lines
11 KiB
Perl

#!/usr/bin/perl
package JMX::Jmx4Perl::Agent::Jolokia::Meta;
use JMX::Jmx4Perl::Agent::Jolokia::DownloadAgent;
use JMX::Jmx4Perl::Agent::Jolokia::Logger;
use JMX::Jmx4Perl::Agent::Jolokia::Verifier;
use JSON;
use Data::Dumper;
use base qw(LWP::UserAgent);
use strict;
my $JOLOKIA_META_URL = "http://www.jolokia.org/jolokia.meta";
=head1 NAME
JMX::Jmx4Perl::Agent::Jolokia::Meta - Fetches, caches and parses Meta data from
www.jolokia.org
=head1 DESCRIPTION
This class is responsible for fetching meta data about available agents from
Jolokia. It knows how to parse those meta data and caches it for subsequent
usage in the local file system.
=head1 METHODS
=over 4
=item $meta = JMX::Jmx4Perl::Agent::Jolokia::Meta->new(....)
Create a new meta object which handles downloading of Jolokia meta information
and caching this data.
=cut
sub new {
my $class = shift;
my $self = ref($_[0]) eq "HASH" ? $_[0] : { @_ };
# Dummy logging if none is provided
$self->{logger} = new JMX::Jmx4Perl::Agent::Jolokia::Logger::None unless $self->{logger};
$self->{verifier} = new JMX::Jmx4Perl::Agent::Jolokia::Verifier(logger => $self->{logger},ua_config => $self->{ua_config});
return bless $self,(ref($class) || $class);
}
=item $meta->load($force)
Load the meta data from the server or retrieve it from the cache. The data is
taken from the cache, if it is no older than $self->{cache_interval} seconds.
If $force is given and true, the data is always fetched fresh from the server.
This method return $self so that it can be used for chaining. Any error or
progress infos are given through to the C<log_handler> provided during
construction time. This method will return C<undef> if the data can't be
loaded.
=cut
sub load {
my ($self,$force) = @_;
$force = $self->{force_load} unless defined($force);
my $meta_json;
my $cached = undef;
if (!$force) {
$meta_json = $self->_from_cache;
$cached = 1 if $meta_json;
}
$meta_json = $self->_load_from_server unless $meta_json; # Throws an error
# if it can't be
# loaded
return undef unless $meta_json;
$self->_to_cache($meta_json) unless $cached;
$self->{_meta} = $meta_json;
return $self;
}
=item $meta->initialized()
Returns C<true> if the meta data has been initialized, either by loading it or
by using a cached data. If false the data can be loaded via L<load>
=cut
sub initialized {
my $self = shift;
return defined($self->{_meta});
}
=item $value = $meta->get($key)
Get a value from the meta data.
=cut
sub get {
my $self = shift;
my $key = shift;
$self->_fatal("No yet loaded") unless $self->{_meta};
return $self->{_meta}->{$key};
}
=item $jolokia_version = $meta->latest_matching_version($jmx4perl_version)
Get the latest matching Jolokia version for a given Jmx4Perl version
=cut
sub latest_matching_version {
my $self = shift;
my $jmx4perl_version = shift;
# Iterate over all existing versions, starting from the newest one,
# and return the first matching
my $version_info = $self->get("versions");
for my $v (sort { $self->compare_versions($b,$a) } grep { $_ !~ /-SNAPSHOT$/ } keys %$version_info) {
my $range = $version_info->{$v}->{jmx4perl};
if ($range) {
my $match = $self->_check_version($jmx4perl_version,$range);
#print "Match: $match for $range (j4p: $jmx4perl_version)\n";
return $v if $match;
}
}
return undef;
}
# Compare two version which can contain one, two or more digits. Returns <0,0 or
# >0 if the first version is smaller, equal or larger than the second version.
# It doesn't take into account snapshot
sub compare_versions {
my $self = shift;
my @first = _split_version(shift);
my @second = _split_version(shift);
my $len = $#first < $#second ? $#first : $#second;
for my $i (0 ... $len) {
next if $first[$i] == $second[$i];
return $first[$i] - $second[$i];
}
return $#first - $#second;
}
sub _split_version {
my $v = shift;
$v =~ s/-.*$//;
return split /\./,$v;
}
sub _check_version {
my $self = shift;
my $jmx4perl_version = shift;
my $range = shift;
my ($l,$l_v,$u_v,$u) = ($1,$2,$3,$4) if $range =~ /^\s*([\[\(])\s*([\d\.]+)\s*,\s*([\d\.]+)\s*([\)\]])\s*$/;
if ($l_v) {
my $cond = "\$a " . ($l eq "[" ? ">=" : ">"). $l_v . " && \$a" . ($u eq "]" ? "<=" : "<") . $u_v;
my $a = $jmx4perl_version;
return eval $cond;
}
return undef;
}
=item $meta->versions_compatible($jmx4perl_version,$jolokia_version)
Check, whether the Jolokia and Jmx4Perl versions are compaptible, i.e.
whether Jmx4Perl with the given version can interoperate with the given
Jolokia version
=cut
sub versions_compatible {
my $self = shift;
my $jmx4perl_version = shift;
my $jolokia_version = shift;
my $version_info = $self->get("versions");
my $range = $version_info->{$jolokia_version}->{jmx4perl};
if ($range) {
return $self->_check_version($jmx4perl_version,$range);
} else {
return undef;
}
}
=item $type = $meta->extract_type($artifact_name)
Extract the type for a given artifactId
=cut
sub extract_type {
my $self = shift;
my $artifact = shift;
my $mapping = $self->get("mapping");
for my $k (keys %$mapping) {
return $k if $mapping->{$k}->[0] eq $artifact;
}
return undef;
}
=item $meta->template_url($template_name,$version)
Download a template with the given name. The download URL is looked up
in the meta data. If a version is given, the template for this specific
version is returned (if present, if not the default template is returned).
If no version is given, the default template is returned. The downloaded
template is verified as any other downloaded artifact.
The template is returned as a string.
=cut
sub template_url {
my $self = shift;
my $template = shift;
my $version = shift;
my $url;
if ($version) {
my $version_info = $self->get("versions");
my $v_data = $version_info->{$version};
$self->_fatal("Cannot load template $template for version $version since $version is unknown")
unless $v_data;
my $templs = $v_data->{templates};
if ($templs) {
$url = $templs->{$template};
}
}
unless ($url) {
my $templs = $self->get("templates");
$self->_fatal("No templates defined in jolokia.meta") unless $templs;
$url = $templs->{$template};
}
return $url;
}
=back
=cut
# ===================================================================================
# Fetch from cache, but only if the cache file is older than $cache_interval
# seconds back in time
sub _from_cache {
my $self = shift;
my $cache_interval = $self->{cache_interval} || 12 * 60 * 60; # 12h by default
my $cache_file = $self->{cache_file} || $ENV{HOME} . "/.jolokia_meta";
my $mtime = (stat($cache_file))[9];
if ($mtime && $mtime >= time - $cache_interval) {
if (!open(F,$cache_file)) {
$self->_error("Cannot open $cache_file: $!");
return undef;
}
my $ret = join "",<F>;
close F;
$self->_debug("Loaded Jolokia meta data from cache");
return from_json($ret,{utf8 => 1});
} else {
return undef;
}
}
# Store to cache
sub _to_cache {
my $self = shift;
my $meta = shift;
my $cache_file = $self->{cache_file} || $ENV{HOME} . "/.jolokia_meta";
if (!open(F,">$cache_file")) {
$self->_error("Cannot save $cache_file: $!");
return;
}
print F to_json($meta,{utf8 => 1,pretty => 1});
close F;
}
# Load from server
sub _load_from_server {
my $self = shift;
# Create sample meta-data
return $self->_example_meta if ($ENV{USE_SAMPLE_JOLOKIA_META});
# Load with HTTP-Client, hardcoded for now
$self->_info("Loading Jolokia meta data from $JOLOKIA_META_URL");
my $ua = new JMX::Jmx4Perl::Agent::Jolokia::DownloadAgent($self->{ua_config});
my $response = $ua->get($JOLOKIA_META_URL);
if ($response->is_success) {
my $content = $response->decoded_content; # or whatever
$self->{verifier}->verify(ua_config => $self->{ua_config}, logger => $self->{logger},
url => $JOLOKIA_META_URL, data => $content);
return from_json($content, {utf8 => 1});
}
else {
# Log an error, but do not exit ...
$self->{logger}->error("Cannot load Jolokia Meta-Data from $JOLOKIA_META_URL: " . $response->status_line);
return undef;
}
}
# Do something with errors and info messages
sub _debug {
shift->{logger}->debug(@_);
}
sub _error {
my $self = shift;
$self->{logger}->error(@_);
}
sub _fatal {
my $self = shift;
$self->{logger}->error(@_);
die "\n";
}
sub _info {
my $self = shift;
$self->{logger}->info(@_);
}
# Sample meta data, also used for creating site meta data.
sub _example_meta {
return {
repositories => [
"http://labs.consol.de/maven/repository"
],
'snapshots-repositories' => [
"http://labs.consol.de/maven/snapshots-repository"
],
versions => {
"0.90-SNAPSHOT" => { jmx4perl => "[0.90,1.0)" },
"0.83" => { jmx4perl => "[0.73,1.0)" },
"0.82" => { jmx4perl => "[0.73,1.0)" } ,
"0.81" => { jmx4perl => "[0.73,1.0)" } ,
},
mapping => {
"war" => [ "jolokia-war", "jolokia-war-%v.war", "jolokia.war" ],
"osgi" => [ "jolokia-osgi", "jolokia-osgi-%v.jar", "jolokia.jar" ],
"osgi-bundle" => [ "jolokia-osgi-bundle", "jolokia-osgi-bundle-%v.jar", "jolokia-bundle.jar" ],
"mule" => [ "jolokia-mule", "jolokia-mule-%v.jar", "jolokia-mule.jar" ],
"jdk6" => [ "jolokia-jvm-jdk6", "jolokia-jvm-jdk6-%v-agent.jar", "jolokia.jar" ]
},
templates => {
"jolokia-access.xml" => "http://www.jolokia.org/templates/jolokia-access.xml"
}
};
}
1;
=head1 LICENSE
This file is part of jmx4perl.
Jmx4perl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
The Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
jmx4perl is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with jmx4perl. If not, see <http://www.gnu.org/licenses/>.
A commercial license is available as well. Please contact roland@cpan.org for
further details.
=head1 AUTHOR
roland@cpan.org
=cut
1;