Commit ccfc19a8 authored by Marek Szuba's avatar Marek Szuba
Browse files

RNAProduct: do not hardcode mappings between type IDs and class names

There is now a dedicated class, Utils::RNAProductTypeMapper, which
provides type_id<->class_name mappings to both RNAProduct constructor
(with the side bonus of no longer having to set the type ID in the
constructor of each subclass) and RNAProductAdaptor. The latter will
likely be used more frequently than the former, therefore the default
map uses type IDs as keys; the reverse map is only generated the first
time it is needed.

In addition to the constructor, the RNAProductTypeMapper.pm defines a
function called mapper() which provides a singleton instance of the
mapper object.

The mappings are hardcoded instead of being retrieved from the database
because the latter could in principle result in execution of arbitrary
code.

Some tests have been defined as well.
parent 3bcb7a52
...@@ -330,15 +330,8 @@ sub _fetch_direct_query { ...@@ -330,15 +330,8 @@ sub _fetch_direct_query {
next; next;
} }
my $class_name; my $class_name = Bio::EnsEMBL::Utils::RNAProductTypeMapper::mapper()
# FIXME: the usual thing about using type_id directly ->type_id_to_class($rnaproduct_type_id);
if ($rnaproduct_type_id == 1) {
$class_name = 'Bio::EnsEMBL::RNAProduct';
} elsif ($rnaproduct_type_id == 2) {
$class_name = 'Bio::EnsEMBL::MicroRNA';
} else {
throw("Unknown rnaproduct type");
}
my $rnaproduct = $class_name->new_fast( { my $rnaproduct = $class_name->new_fast( {
'dbID' => $rnaproduct_id, 'dbID' => $rnaproduct_id,
'type_id' => $rnaproduct_type_id, 'type_id' => $rnaproduct_type_id,
......
...@@ -95,9 +95,6 @@ sub new { ## no critic (Subroutines::RequireArgUnpacking) ...@@ -95,9 +95,6 @@ sub new { ## no critic (Subroutines::RequireArgUnpacking)
my $self = $class->SUPER::new(@_); my $self = $class->SUPER::new(@_);
# FIXME: see the comment about same in RNAProduct::new()
my $type_id = 2;
my ($arm) = rearrange(["ARM"], @_); my ($arm) = rearrange(["ARM"], @_);
if (defined($arm)) { if (defined($arm)) {
_validate_arm_value($arm); _validate_arm_value($arm);
......
...@@ -61,6 +61,7 @@ use warnings; ...@@ -61,6 +61,7 @@ use warnings;
use Bio::EnsEMBL::Utils::Exception qw(throw warning ); use Bio::EnsEMBL::Utils::Exception qw(throw warning );
use Bio::EnsEMBL::Utils::Argument qw( rearrange ); use Bio::EnsEMBL::Utils::Argument qw( rearrange );
use Bio::EnsEMBL::Utils::RNAProductTypeMapper;
use Bio::EnsEMBL::Utils::Scalar qw( assert_ref wrap_array ); use Bio::EnsEMBL::Utils::Scalar qw( assert_ref wrap_array );
use Scalar::Util qw(weaken); use Scalar::Util qw(weaken);
...@@ -101,11 +102,8 @@ sub new { ## no critic (Subroutines::RequireArgUnpacking) ...@@ -101,11 +102,8 @@ sub new { ## no critic (Subroutines::RequireArgUnpacking)
my $class = ref($caller) || $caller; my $class = ref($caller) || $caller;
# For an unspecialised rnaproduct object, set the type to "generic mature my $type_id = Bio::EnsEMBL::Utils::RNAProductTypeMapper::mapper()
# RNA". Ideally we would look the corresponding ID up in rnaproduct_type, ->class_to_type_id($class);
# then again that would make this dependent on the database connection...
# Maybe we should just store a string code instead? Either way, FIXME.
my $type_id = 1;
my ($seq_start, $seq_end, $stable_id, $version, $dbID, $adaptor, $seq, my ($seq_start, $seq_end, $stable_id, $version, $dbID, $adaptor, $seq,
$created_date, $modified_date ) = $created_date, $modified_date ) =
......
=head1 LICENSE
Copyright [2018] EMBL-European Bioinformatics Institute
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
=cut
=head1 CONTACT
Please email comments or questions to the public Ensembl
developers list at <http://lists.ensembl.org/mailman/listinfo/dev>.
Questions may also be sent to the Ensembl help desk at
<http://www.ensembl.org/Help/Contact>.
=cut
=head1 NAME
Bio::EnsEMBL::Utils::RNAProductTypeMapper - Utility class for mapping
between RNA-product types used in the Ensembl database and respective
Perl API classes.
=head1 DESCRIPTION
The purpose of this class is to hide from the users the mappings
between classes representing various mature RNA products
(e.g. generic, miRNA, circRNA etc.) and their respective identifiers
in the Ensembl database. In principle there should be no need for
users to call the mapper directly; it is invoked internally whenever
such mappings are needed (e.g. in RNAProduct constructor or in
RNAProductAdaptor).
Note that the type_id<->class_name mappings are hardcoded here,
specifically in the constructor, instead of being fetched from the
database. This is so that it is not possible for someone to trigger
construction of arbitrary objects by having modified class names
stored in the database.
=head1 SYNOPSIS
my $rpt_mapper = Bio::EnsEMBL::Utils::RNAProductTypeMapper->new();
my $class = $rpt_mapper->type_id_to_class( 1 );
my $type_id = $rpt_mapper->class_to_type_id( 'Bio::EnsEMBL::MicroRNA' );
=cut
package Bio::EnsEMBL::Utils::RNAProductTypeMapper;
use strict;
use warnings;
use Bio::EnsEMBL::Utils::Exception qw( throw warning );
my $type_mapper;
=head2 mapper
Example : my $mapper = Bio::EnsEMBL::Utils::RNAProductTypeMapper->mapper();
Description: Retrieves an instance of RNAProductTypeMapper from its module,
reusing an existing one should it exist.
Returntype : Bio::EnsEMBL::Utils::RNAProductTypeMapper
Exceptions : none
Caller : internal
Status : Stable
=cut
sub mapper {
if ( !defined($type_mapper) ) {
$type_mapper = Bio::EnsEMBL::Utils::RNAProductTypeMapper->new();
}
return $type_mapper;
}
=head2 new
Example : my $mapper = Bio::EnsEMBL::Utils::RNAProductTypeMapper->new();
Description: Constructor. Creates a new RNAProductTypeMapper object.
Not particularly useful for end users because all
RNAProductTypeMapper objects are identical; use mapper()
instead.
Returntype : Bio::EnsEMBL::Utils::RNAProductTypeMapper
Exceptions : none
Caller : internal
Status : Stable
=cut
sub new {
my ( $caller ) = @_;
my $class = ref( $caller ) || $caller;
# Declare this here rather than in the package scope so that the map
# cannot be modified (we do not presently use Readonly in the Core
# API code), accidentally or otherwise.
my $id_to_class_map = {
1 => 'Bio::EnsEMBL::RNAProduct',
2 => 'Bio::EnsEMBL::MicroRNA',
};
my $self = bless {
'id_to_class_map' => $id_to_class_map,
'class_to_id_map' => undef,
}, $class;
return $self;
}
=head2 class_to_type_id
Arg [1] : string $class_name - fully qualified rnaproduct class name
Example : my $type_id
= $mapper->class_to_type_id( 'Bio::EnsEMBL::MicroRNA' );
Description: For the given name of a class representing a mature RNA
product, returns the type ID used to reference it in the
Ensembl database.
Returntype : int
Exceptions : throw if the class does not represent known RNA-product type
Caller : internal
Status : Stable
=cut
sub class_to_type_id {
my ( $self, $class_name ) = @_;
if ( !defined( $self->{'class_to_id_map'} ) ) {
$self->_generate_reverse_map();
}
my %map = %{ $self->{'class_to_id_map'} };
if ( !exists $map{$class_name} ) {
throw( "Unknown RNA-product class name " . $class_name );
}
return $map{$class_name};
}
=head2 type_id_to_class
Arg [1] : int $type_id - internal type identified of rnaproduct
Example : my $class_name = $mapper->class_to_type_id( 1 );
Description: For the type ID referencing a mature RNA product in the
Ensembl database, return its API class name
Returntype : string
Exceptions : throw if the ID does not represent known RNA-product type
Caller : internal
Status : Stable
=cut
sub type_id_to_class {
my ( $self, $type_id ) = @_;
my %map = %{ $self->{'id_to_class_map'} };
if ( !exists $map{$type_id} ) {
throw( "Unknown RNA-product type ID " . $type_id );
}
return $map{$type_id};
}
# _generate_reverse_map
# Description: PRIVATE generates class_name->type_id map from the
# type_id->class_name one.
# Returntype : none
# Exceptions : none
# Caller : internal
# Status : Stable
sub _generate_reverse_map {
my ($self) = @_;
# Safe to use reverse because both keys and values are unique
my %reversed_map = reverse %{$self->{'id_to_class_map'}};
$self->{'class_to_id_map'} = \%reversed_map;
return;
}
1;
...@@ -327,6 +327,28 @@ subtest 'MicroRNA tests' => sub { ...@@ -327,6 +327,28 @@ subtest 'MicroRNA tests' => sub {
}; };
#
# Tests for the RNAProduct-type adaptor
#######################################
subtest 'RNAProductTypeMapper tests' => sub {
my $rpt_mapper = Bio::EnsEMBL::Utils::RNAProductTypeMapper->mapper();
my $rpt_mapper2 = Bio::EnsEMBL::Utils::RNAProductTypeMapper->mapper();
is($rpt_mapper, $rpt_mapper2, 'mapper() reuses existing instance if present');
is($rpt_mapper->type_id_to_class(2), 'Bio::EnsEMBL::MicroRNA',
'Can map existing type ID to class');
dies_ok(sub { $rpt_mapper->type_id_to_class(34356); },
'Exception thrown on unknown type ID');
is($rpt_mapper->class_to_type_id('Bio::EnsEMBL::RNAProduct'), 1,
'Can map existing class to type ID');
dies_ok(sub { $rpt_mapper->class_to_type_id('Bio::EnsEMBL::Storable'); },
'Exception thrown on unknown rnaproduct class name');
};
done_testing(); done_testing();
1; 1;
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment