#----------------------------------------------------------------- # MRS::Client::Databank # Authors: Martin Senger <martin.senger@gmail.com> # For copyright and disclaimer see MRS::Client pod. # # ABSTRACT: Representation of a MRS databank - on a client side # PODNAME: MRS::Client #----------------------------------------------------------------- use warnings; use strict; package MRS::Client::Databank; our $VERSION = '1.0.1'; # VERSION use Carp; use MRS::Constants; use Data::Dumper; #----------------------------------------------------------------- # Mandatory argument is an 'id' defining what databank should be # created. However, this method does not need to be called directly: # better to use factory method db() of MRS::Client. # ----------------------------------------------------------------- sub new { my ($class, %args) = @_; # create an object and fill it from $args my $self = bless {}, ref ($class) || $class; foreach my $key (keys %args) { $self->{$key} = $args {$key}; } # check that we have at least an ID croak ("The MRS::Client::Databank instance cannot be created without an ID.\n") unless $self->{id}; # done return $self; } #----------------------------------------------------------------- # Getter. Most of them first fill the databank from the server. # ----------------------------------------------------------------- sub id { return shift->{id}; } sub name { return shift->_populate_info->{name}; } sub blastable { return shift->_populate_info->{blastable}; } sub url { return shift->_populate_info->{url}; } sub parser { my $self = shift; if ($self->{client}->is_v6) { return $self->_populate_info->{parser}; } else { return $self->_populate_info->{script}; } } sub files { return shift->_populate_info->{files}; } sub indices { return shift->_populate_indices->{indices}; } sub count { return shift->_populate_count->{count}; } sub version { my $self = shift; if ($self->{client}->is_v6) { return $self->_populate_info->{version}; } else { my $r = ''; if ($self->files) { foreach my $file (@{ $self->files }) { $r .= ', ' if $r; $r .= $file->version; } } return $r; } } # returns a meaningful result (an arrayref) only from MRS 6 and above sub aliases { return shift->_populate_info->{aliases}; } #----------------------------------------------------------------- # Mostly for debugging - because it may be expensive: It calls several # SOAP operations to fill first the databank. # ----------------------------------------------------------------- use overload q("") => "as_string"; sub as_string { my $self = shift; $self->_populate_info(); my $r = ''; $r .= "Id: " . $self->{id} . "\n"; $r .= "Name: " . $self->name . "\n" if $self->{name}; $r .= "Version: " . $self->version . "\n"; $r .= "Count: " . $self->count . "\n"; $r .= "URL: " . $self->{url} . "\n" if $self->{url}; $r .= "Parser: " . $self->parser . "\n" if $self->parser; $r .= "blastable\n" if $self->{blastable}; $r .= "Aliases: " . join (", ", @{ $self->aliases } ) . "\n" if $self->aliases; $r .= "Files:\n\t" . join ("\n\t", map { my $file = $_; $file =~ s/\n/\n\t/g; $file } @{ $self->files } ) . "\n"; $r .= "Indices:\n\t" . join ("\n\t", @{ $self->indices } ) . "\n"; return $r; } #----------------------------------------------------------------- # If this instance does not have yet info data then populate them. # Return itself (the databank instance). #----------------------------------------------------------------- sub _populate_info { my $self = shift; return $self if $self->{info_processed}; # it may already be retrieved by the client->db() method for 'all' unless ($self->{info_retrieved}) { $self->{client}->_create_proxy ('search'); my $answer = $self->{client}->_call ( $self->{client}->{search_proxy}, 'GetDatabankInfo', { db => $self->{id} }); if (defined $answer) { my $is_alias = ( @{ $answer->{parameters}->{info} } > 1 ); my $entries = 0; my $rawDataSize = 0; my $fileSize = 0; foreach my $info (@{ $answer->{parameters}->{info} }) { foreach my $key (keys %$info) { if ($key eq 'indices') { # special dealing with indices $self->{indices} = [] unless exists $self->{indices}; foreach my $ind (@{ $info->{$key} }) { push (@{ $self->{indices} }, MRS::Client::Databank::Index->new (%$ind, db => $info->{id})); } next; } if ($is_alias) { # deal with numeric fields if ($key eq 'entries') { $entries += $info->{$key}; } elsif ($key eq 'rawDataSize') { $rawDataSize += $info->{$key}; } elsif ($key eq 'fileSize') { $fileSize += $info->{$key}; } elsif ($key eq 'aliases' or $key eq 'id') { # ...and ignore aliases and ID when dealing with an alias } else { # ...and concatenate those string fields that are differnt if (exists $self->{$key} and $self->{$key} ne $info->{$key}) { $self->{$key} .= ", $info->{$key}"; } else { $self->{$key} = $info->{$key}; } } } else { # this databank is NOT an alias $self->{$key} = $info->{$key}; } } } if ($is_alias) { $self->{entries} = $entries; $self->{rawDataSize} = $rawDataSize; $self->{fileSize} = $fileSize; } } $self->{info_retrieved} = 1; } # special treatment for 'files': create File objects if ($self->{client}->is_v6) { my $file = {}; $file->{rawDataSize} = $self->{rawDataSize} if defined $self->{rawDataSize}; $file->{modificationDate} = $self->{modificationDate} if defined $self->{modificationDate}; $file->{fileSize} = $self->{fileSize} if defined $self->{fileSize}; $file->{entries} = $self->{entries} if defined $self->{entries}; $file->{version} = $self->{version} if defined $self->{version}; $file->{uuid} = $self->{uuid} if defined $self->{uuid}; $self->{files} = [$file]; } $self->{files} = [ map { MRS::Client::Databank::File->new (%$_) } @{ $self->{files} } ]; $self->{info_processed} = 1; return $self; } #----------------------------------------------------------------- # If this instance does not have yet indices then populate them. # Return itself (the databank instance). #----------------------------------------------------------------- sub _populate_indices { my $self = shift; return $self if $self->{indices_retrieved}; if ($self->{client}->is_v6) { $self->_populate_info(); } else { $self->{client}->_create_proxy ('search'); my $answer = $self->{client}->_call ( $self->{client}->{search_proxy}, 'GetIndices', { db => $self->{id} }); $self->{indices_retrieved} = 1; if (defined $answer) { $self->{indices} = [ map { MRS::Client::Databank::Index->new (%$_, db => $self->id) } @{ $answer->{parameters}->{indices} } ]; } } return $self; } #----------------------------------------------------------------- # If this instance does not have yet its count then populate it. # Return itself (the databank instance). #----------------------------------------------------------------- sub _populate_count { my $self = shift; return $self if defined $self->{count}; if (defined $self->{entries}) { $self->{count} = $self->{entries}; return $self; } if ($self->{client}->is_v6) { $self->_populate_info(); $self->{count} = $self->{entries}; } else { $self->{client}->_create_proxy ('search'); my $answer = $self->{client}->_call ( $self->{client}->{search_proxy}, 'Count', { db => $self->{id}, booleanquery => '*'}); # print Dumper ($answer); if (defined $answer) { $self->{count} = $answer->{parameters}->{response}; } else { $self->{count} = 0; } } return $self; } #----------------------------------------------------------------- # Make a query. See MRS::Client::Find->new about the parameters. #----------------------------------------------------------------- sub find { my $self = shift; my $find = MRS::Client::Find->new ($self->{client}, @_); $find->{db} = $self->{id}; $find->{dbobj} = $self; my $record = $find->_read_next_hits; unshift (@{ $find->{hits} }, $record) if $record; return $find; } #----------------------------------------------------------------- # Get an entry defined by $entry_id in the $format (optional). Some # formats may have extended options in $xformat. # ----------------------------------------------------------------- sub entry { my ($self, $entry_id, $format, $xformat) = @_; croak "Empty entry ID. Cannot do anything, I am afraid.\n" unless $entry_id; $format = MRS::EntryFormat->PLAIN unless MRS::EntryFormat->check ($format, $self->{client}); warn ("Method 'entry' does not support format HEADER. Reversed to TITLE.\n") and $format = MRS::EntryFormat->TITLE if $format eq MRS::EntryFormat->HEADER; $self->{client}->_create_proxy ('search'); my $answer = $self->{client}->_call ( $self->{client}->{search_proxy}, 'GetEntry', { db => $self->{id}, id => $entry_id, format => $format }); return '' unless defined $answer; if ($xformat and $format eq MRS::EntryFormat->HTML) { return $self->_xformat ($xformat, $answer->{parameters}->{entry}); } else { return $answer->{parameters}->{entry}; } } # sub _xformat { my ($self, $xformat, $html) = @_; # in these case, the returned content will be different from the given $html my $change_wanted = ( $xformat->{MRS::XFormat::CSS_CLASS()} or $xformat->{MRS::XFormat::REMOVE_DEAD()} or $xformat->{MRS::XFormat::URL_PREFIX} ); # in this case, we need a list of available databanks # (which may be already provided in $xformat itself) if ($xformat->{MRS::XFormat::REMOVE_DEAD()}) { if (ref ($xformat->{MRS::XFormat::REMOVE_DEAD()}) ne 'ARRAY' ) { $xformat->{MRS::XFormat::REMOVE_DEAD()} = [map { $_->id } $self->{client}->db]; } # internally, change it to a hashref $xformat->{'_dbs_'} = { map { $_ => 1 } @{ $xformat->{MRS::XFormat::REMOVE_DEAD()} } }; } my $regex = '(<a (?:.+?)</a>)'; if ($xformat->{MRS::XFormat::ONLY_LINKS()}) { my @links = ( $html =~ m{$regex}migo ); if ($change_wanted) { return [ map { $self->_change_link ($xformat, $_) } @links ]; } else { return \@links } } else { $html =~ s{$regex}{$self->_change_link ($xformat, $1)}emigo; return $html; } } # sub _change_link { my ($self, $xformat, $link) = @_; if (my $class = $xformat->{css_class}) { $link =~ s/(<a )/$1class="$class" /oi; } if ($xformat->{url_prefix}) { $link =~ s{(href=")(query|entry)}{$1$xformat->{url_prefix}$2}oi; } if ($xformat->{remove_dead_links}) { my ($db) = $link =~ m{[.]do[?]db=(\w+?)&}o; if ($db and not $xformat->{'_dbs_'}->{$db}) { $link =~ s{<[^>]*>}{}g; } } return $link; } #----------------------------------------------------------------- # # MRS::Client::Databank::File ... info about a file of a databank # #----------------------------------------------------------------- package MRS::Client::Databank::File; our $VERSION = '1.0.1'; # VERSION sub new { my ($class, %file) = @_; # create an object and fill it from $file my $self = bless {}, ref ($class) || $class; foreach my $key (keys %file) { $self->{$key} = $file {$key}; } # done return $self; } sub id { return shift->{uuid}; } sub raw_data_size { return shift->{rawDataSize}; } sub entries_count { return shift->{entries}; } sub file_size { return shift->{fileSize}; } sub version { return shift->{version}; } sub last_modified { return shift->{modificationDate}; } use overload q("") => "as_string"; sub as_string { my $self = shift; "Version: " . $self->version . "\n" . "Modified: " . $self->last_modified . "\n" . "Entries count: " . $self->entries_count . "\n" . "Raw data size: " . $self->raw_data_size . "\n" . "File size: " . $self->file_size . "\n" . "Unique Id: " . $self->id ; } #----------------------------------------------------------------- # # MRS::Client::Databank::Index # #----------------------------------------------------------------- package MRS::Client::Databank::Index; our $VERSION = '1.0.1'; # VERSION sub new { my ($class, %args) = @_; # create an object and fill it from $args my $self = bless {}, ref ($class) || $class; foreach my $key (keys %args) { $self->{$key} = $args {$key}; } # done return $self; } sub db { return (shift->{db} or ''); } sub id { return shift->{id}; } sub description { return shift->{description}; } sub count { return shift->{count} } sub type { return shift->{type}; } use overload q("") => "as_string"; sub as_string { my $self = shift; return sprintf ( "%-15s%-15s%9d %-9s %s", $self->db, $self->id, $self->count, $self->type, $self->description); } 1; =pod =head1 NAME MRS::Client - Representation of a MRS databank - on a client side =head1 VERSION version 1.0.1 =head1 NAME MRS::Client::Databank - part of a SOAP-based client accessing MRS databases =head1 REDIRECT For the full documentation of the project see please: perldoc MRS::Client =head1 AUTHOR Martin Senger <martin.senger@gmail.com> =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2013 by Martin Senger, CBRC - KAUST (Computational Biology Research Center - King Abdullah University of Science and Technology) All Rights Reserved.. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut __END__