Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
E
ensembl
Manage
Activity
Members
Labels
Plan
Issues
0
Issue boards
Milestones
Iterations
Wiki
Requirements
Jira
Code
Merge requests
1
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Build
Pipelines
Jobs
Pipeline schedules
Test cases
Artifacts
Deploy
Releases
Package Registry
Container Registry
Operate
Environments
Terraform modules
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Code review analytics
Issue analytics
Insights
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
ensembl-gh-mirror
ensembl
Commits
6f907f92
Commit
6f907f92
authored
21 years ago
by
Graham McVicker
Browse files
Options
Downloads
Patches
Plain Diff
Merged in Steve Searle's magical connection management code (via disconnect_when_inactive flag)
parent
2ca72595
No related branches found
Branches containing commit
No related tags found
Tags containing commit
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
modules/Bio/EnsEMBL/DBSQL/DBConnection.pm
+261
-215
261 additions, 215 deletions
modules/Bio/EnsEMBL/DBSQL/DBConnection.pm
modules/Bio/EnsEMBL/DBSQL/StatementHandle.pm
+62
-0
62 additions, 0 deletions
modules/Bio/EnsEMBL/DBSQL/StatementHandle.pm
with
323 additions
and
215 deletions
modules/Bio/EnsEMBL/DBSQL/DBConnection.pm
+
261
−
215
View file @
6f907f92
#=H
ead1 NAME - Bio::EnsEMBL::DBSQL::DBConnection
=h
ead1 NAME - Bio::EnsEMBL::DBSQL::DBConnection
=head1 SYNOPSIS
$db = Bio::EnsEMBL::DBSQL::DBConnection->new(
-user => '
root
',
-dbname => '
pog
',
-host => '
caldy
',
-user => '
anonymous
',
-dbname => '
homo_sapiens_core_20_34c
',
-host => '
ensembldb.ensembl.org
',
-driver => 'mysql',
);
You should use this as a base class for all objects (DBAdaptor) that
connect to a database.
connect to a database. SQL statements should be created/executed through
this modules prepare() and do() methods.
$sth = $db->prepare( "SELECT something FROM yourtable" );
If you go through prepare you could log all your select statements.
If you want to share a database handle with other database connections
you can do the following:
$db2 = Bio::EnsEMBL::DBSQL::DBConnection->new(-dbconn => $db);
=head1 DESCRIPTION
This only wraps around the perl DBI->connect call,
so you dont have to remember how to do this.
This class is a wrapper around DBIs datbase handle. It provides some
additional functionality such as the ability to automatically disconnect
when inactive and reconnect when needed, and a way to obtain ObjectAdaptors.
This class is intended to be inherited from rather than used directly.
See Bio::EnsEMBL::DBSQL::DBAdaptor.
=head1 CONTACT
...
...
@@ -47,10 +44,12 @@ use Bio::EnsEMBL::Container;
use
Bio::EnsEMBL::
Root
;
use
DBI
;
use
Bio::EnsEMBL::DBSQL::
StatementHandle
;
use
Bio::EnsEMBL::Utils::
Exception
qw(throw info warning)
;
use
Bio::EnsEMBL::Utils::
Argument
qw(rearrange)
;
@ISA
=
qw(Bio::EnsEMBL::Root)
;
@ISA
=
qw(Bio::EnsEMBL::Root)
;
# for backwards compatibility
=head2 new
...
...
@@ -70,16 +69,26 @@ use Bio::EnsEMBL::Utils::Argument qw(rearrange);
Arg [DRIVER] : (optional) string
The type of database driver to use to connect to the DB
mysql by default.
Arg [DBCONN] : (optional)
Open another handle to the same database as another connection
If this argument is specified, no other arguments should be
specified.
Arg [DISCONNECT_WHEN_INACTIVE]: (optional) boolean
If set to true, the database connection will be disconnected
everytime there are no active statement handles. This is
useful when running a lot of jobs on a compute farm
which would otherwise keep open a lot of connections to the
database. Database connections are automatically reopened
when required.
Example : $dbc = Bio::EnsEMBL::DBSQL::DBConnection->new
(-user=> 'anonymous',
-dbname => '
pog
',
-host => '
caldy
',
(-user
=> 'anonymous',
-dbname => '
homo_sapiens_core_20_34c
',
-host => '
ensembldb.ensembl.org
',
-driver => 'mysql');
$dbc2 = Bio::EnsEMBL::DBSQL::DBConnection->new(-DBCONN => $dbc);
Description: Constructor for a DatabaseConenction. Any adaptors that require
database connectivity should inherit from this class.
Returntype : Bio::EnsEMBL::DBSQL::DBConnection
Returntype : Bio::EnsEMBL::DBSQL::DBConnection
Exceptions : thrown if USER or DBNAME are not specified, or if the database
cannot be connected to.
Caller : Bio::EnsEMBL::DBSQL::DBAdaptor
...
...
@@ -89,82 +98,68 @@ use Bio::EnsEMBL::Utils::Argument qw(rearrange);
sub
new
{
my
$class
=
shift
;
my
(
$db
,
$dbconn
,
$host
,
$driver
,
$user
,
$password
,
$port
)
=
rearrange
([
qw(DBNAME DBCONN HOST DRIVER USER PASS PORT)
],
@
_
);
my
(
$db
,
$host
,
$driver
,
$user
,
$password
,
$port
,
$inactive_disconnect
,
$dbconn
)
=
rearrange
([
qw(DBNAME HOST DRIVER USER PASS PORT
DISCONNECT_WHEN_INACTIVE DBCONN)
],
@
_
);
my
$self
=
{};
bless
$self
,
$class
;
if
(
$dbconn
)
{
if
(
!
ref
(
$dbconn
)
||
!
$d
bconn
->
isa
('
Bio::EnsEMBL::DBSQL::DBC
onnect
ion
')
)
{
throw
("
Bio::EnsEMBL::DBSQL::DBConnection argument expect
ed.
");
if
(
$db
||
$host
||
$d
river
||
$password
||
$port
||
$inactive_disc
onnect
)
{
throw
("
Cannot specify other arguments when -DBCONN argument us
ed.
");
}
#share a common db_handle, use a shared scalar ref_count to track # connections
my
$rcount
=
$dbconn
->
ref_count
();
$$rcount
++
;
# dereference and increment shared var
$self
->
ref_count
(
$rcount
);
$self
->
db_handle
(
$dbconn
->
db_handle
());
$self
->
driver
(
$dbconn
->
driver
());
$self
->
host
(
$dbconn
->
host
());
$self
->
port
(
$dbconn
->
port
());
$self
->
dbname
(
$dbconn
->
dbname
());
$self
->
username
(
$dbconn
->
username
());
$self
->
host
(
$dbconn
->
host
());
$self
->
password
(
$dbconn
->
password
());
$self
->
port
(
$dbconn
->
port
());
$self
->
driver
(
$dbconn
->
driver
());
return
Bio::EnsEMBL::
Container
->
new
(
$self
);
}
$self
->
connect
();
$db
||
throw
("
Database object must have a database name
");
$user
||
throw
("
Database object must have a user
");
if
(
$dbconn
->
disconnect_when_inactive
())
{
$self
->
disconnect_when_inactive
(
1
)
}
}
else
{
if
(
!
$driver
)
{
$driver
=
'
mysql
';
}
if
(
!
$host
)
{
$host
=
'
localhost
';
}
if
(
!
$port
)
{
$port
=
3306
;
}
$db
||
throw
("
-DBNAME argument is required.
");
$user
||
throw
("
-USER argument is required.
");
=head1
my $dsn = "DBI:$driver:database=$db;host=$host;port=$port";
$driver
||=
'
mysql
';
$host
||=
'
mysql
';
$port
||=
3306
;
my $dbh;
eval{
$dbh = DBI->connect("$dsn","$user",$password, {RaiseError => 1});
};
my
$dsn
=
"
DBI:
$driver
:database=
$db
;host=
$host
;port=
$port
";
$dbh || throw("Could not connect to database $db user " .
"$user using [$dsn] as a locator\n" . $DBI::errstr);
$self
->
username
(
$user
);
$self
->
host
(
$host
);
$self
->
dbname
(
$db
);
$self
->
password
(
$password
);
$self
->
port
(
$port
);
$self
->
driver
(
$driver
);
my $ref_count = 1;
$self->ref_count(\$ref_count);
$self->db_handle($dbh);
=cut
$self
->
connect
();
if
(
$inactive_disconnect
)
{
$self
->
disconnect_when_inactive
(
$inactive_disconnect
);
}
# be very sneaky and actually return a container object which is outside
# of the circular reference loops and will perform cleanup when all
# references to the container are gone.
}
$self
->
username
(
$user
);
$self
->
host
(
$host
);
$self
->
dbname
(
$db
);
$self
->
password
(
$password
);
$self
->
port
(
$port
);
$self
->
driver
(
$driver
);
$self
->
connect
();
# be very sneaky and actually return a container object which is outside
# of the circular reference loops and will perform cleanup when all
# references to the container are gone.
return
new
Bio::EnsEMBL::
Container
(
$self
);
}
=head2 connect
Example : $db_connection->connect()
Description: Explicitely connect to database if not connected
Example : $dbcon->connect()
Description: Connects to the database using the connection attribute
information.
Returntype : none
Exceptions : none
Caller : new, db_handle
...
...
@@ -174,29 +169,26 @@ sub new {
sub
connect
{
my
$self
=
shift
;
if
(
$self
->
{'
_db_handle
'})
{
return
;
}
my
$host
=
$self
->
host
();
my
$driver
=
$self
->
driver
();
my
$user
=
$self
->
username
();
my
$password
=
$self
->
password
();
my
$port
=
$self
->
port
();
my
$db
=
$self
->
dbname
();
my
$dsn
=
"
DBI:
$driver
:database=
$db
;host=
$host
;port=
$port
";
my
$dsn
=
"
DBI:
"
.
$self
->
driver
()
.
"
:database=
"
.
$self
->
dbname
()
.
"
;host=
"
.
$self
->
host
()
.
"
;port=
"
.
$self
->
port
();
my
$dbh
;
eval
{
$dbh
=
DBI
->
connect
("
$dsn
","
$user
",
$password
,
{
RaiseError
=>
1
});
$dbh
=
DBI
->
connect
(
$dsn
,
$self
->
username
(),
$self
->
password
(),
{
RaiseError
=>
1
});
};
$dbh
||
throw
("
Could not connect to database
$db
user
"
.
"
$user
using [
$dsn
] as a locator
\n
"
.
$
DBI::
errstr
);
$dbh
||
throw
("
Could not connect to database
"
.
$self
->
dbname
()
.
"
as user
"
.
$self
->
username
()
.
"
using [
$dsn
] as a locator:
\n
"
.
$
DBI::
errstr
);
$self
->
db_handle
(
$dbh
);
#print STDERR "DBConnection : CONNECT\n";
my
$ref_count
=
1
;
$self
->
ref_count
(
\
$ref_count
);
$self
->
{'
_db_handle
'}
=
$dbh
;
return
;
}
...
...
@@ -339,35 +331,40 @@ sub password {
}
=head2
ref_count
=head2
disconnect_when_inactive
Arg [1] : (optional)
ref to int $ref_count
Example : $
count = 1; $self->ref_count(\$count
);
Description: Getter/
s
etter for the
number of existing references to
this DBConnection
s database handle. This is a reference to
a scalar because it is shared by all database connections which
share the same database handle. This is used by the DESTROY
m
ethod to decide whether it should disconnect from the databas
e.
Returntype :
reference to int
Exceptions :
throw on bad argument
Caller : ne
w
Arg [1] : (optional)
boolean $newval
Example : $
db->disconnect_when_inactive(1
);
Description: Getter/
S
etter for the
disconnect_when_inactive flag. If set
to true
this DBConnection
will continually disconnect itself
when there are no active statement handles and reconnect as
necessary. Useful for farm environments when there can be
m
any (often inactive) open connections to a database at onc
e.
Returntype :
boolean
Exceptions :
none
Caller :
Pipeli
ne
=cut
sub
ref_count
{
sub
disconnect_when_inactive
{
my
$self
=
shift
;
if
(
@
_
)
{
my
$ref_count
=
shift
;
if
(
ref
(
$ref_count
)
ne
'
SCALAR
')
{
throw
("
Reference to scalar argument expected.
");
my
$val
=
shift
;
$self
->
{'
disconnect_when_inactive
'}
=
$val
;
if
(
$val
)
{
$self
->
disconnect_if_idle
();
}
elsif
(
!
$self
->
db_handle
->
ping
())
{
# reconnect if the connect went away
$self
->
connect
();
}
$self
->
{'
ref_count
'}
=
$ref_count
;
}
return
$self
->
{'
ref_count
'};
return
$self
->
{'
disconnect_when_inactive
'};
}
=head2 locator
Arg [1] : none
...
...
@@ -384,19 +381,148 @@ sub ref_count {
sub
locator
{
my
$self
=
shift
;
my
$ref
;
if
(
$self
->
isa
('
Bio::EnsEMBL::Container
'))
{
$ref
=
ref
(
$self
->
_obj
);
}
else
{
$ref
=
ref
(
$self
);
}
my
$ref
=
ref
(
$self
);
return
"
$ref
/host=
"
.
$self
->
host
.
"
;port=
"
.
$self
->
port
.
"
;dbname=
"
.
$self
->
dbname
.
"
;user=
"
.
$self
->
username
.
"
;pass=
"
.
$self
->
password
;
}
=head2 db_handle
Arg [1] : DBI Database Handle $value
Example : $dbh = $db_connection->db_handle()
Description: Getter / Setter for the Database handle used by this
database connection.
Returntype : DBI Database Handle
Exceptions : none
Caller : new, DESTROY
=cut
sub
db_handle
{
my
$self
=
shift
;
$self
->
{'
db_handle
'}
=
shift
if
(
@
_
);
return
$self
->
{'
db_handle
'};
}
=head2 prepare
Arg [1] : string $string
the SQL statement to prepare
Example : $sth = $db_connection->prepare("SELECT column FROM table");
Description: Prepares a SQL statement using the internal DBI database handle
and returns the DBI statement handle.
Returntype : DBI statement handle
Exceptions : thrown if the SQL statement is empty, or if the internal
database handle is not present
Caller : Adaptor modules
=cut
sub
prepare
{
my
(
$self
,
$string
)
=
@_
;
if
(
!
$string
)
{
throw
("
Attempting to prepare an empty SQL query.
");
}
if
(
!
defined
(
$self
->
db_handle
)
)
{
throw
("
Database object has lost its database handle.
");
}
if
(
$self
->
disconnect_when_inactive
()
&&
!
$self
->
db_handle
->
ping
())
{
# reconnect if we have been disconnected
$self
->
connect
();
}
#info("SQL(".$self->dbname."):$string");
my
$sth
=
$self
->
db_handle
->
prepare
(
$string
);
# return an overridden statement handle that provides us with
# the means to disconnect inactive statement handles automatically
bless
$sth
,
"
Bio::EnsEMBL::DBSQL::StatementHandle
";
$sth
->
dbc
(
$self
);
return
$sth
;
}
=head2 do
Arg [1] : string $string
the SQL statement to prepare
Example : $sth = $db_connection->do("SELECT column FROM table");
Description: Executes a SQL statement using the internal DBI database handle.
Returntype : Result of DBI dbh do() method
Exceptions : thrown if the SQL statement is empty, or if the internal
database handle is not present.
Caller : Adaptor modules
=cut
sub
do
{
my
(
$self
,
$string
)
=
@_
;
if
(
!
$string
)
{
throw
("
Attempting to do an empty SQL query.
");
}
if
(
!
defined
(
$self
->
db_handle
)
)
{
throw
("
Database object has lost its database handle.
");
}
if
(
$self
->
disconnect_when_inactive
()
&&
!
$self
->
db_handle
->
ping
())
{
# reconnect if we have been disconnected
$self
->
connect
();
}
#info("SQL(".$self->dbname."):$string");
my
$result
=
$self
->
db_handle
->
do
(
$string
);
# disconnect if the disconnect when inactive flag is set and
# there are no active statement handles
if
(
$self
->
disconnect_when_inactive
())
{
$self
->
disconnect_if_idle
();
}
return
$result
;
}
=head2 disconnect_if_idle
Arg [1] : none
Example : $dbc->disconnect_if_idle();
Description: Disconnects from the database if there are no currently active
statement handles. You should not call this method directly.
It is called automatically by the DESTROY method of the
Bio::EnsEMBL::DBSQL::SQL::StatementHandle if the
disconect_when_inactive flag is set.
Returntype : none
Exceptions : none
Caller : Bio::EnsEMBL::DBSQL::SQL::StatementHandle::DESTROY
Bio::EnsEMBL::DBSQL::DBConnection::do
=cut
sub
disconnect_if_idle
{
my
$self
=
shift
;
if
(
!
$self
->
db_handle
()
->
{'
ActiveKids
'}
&&
!
$self
->
db_handle
()
->
{'
InactiveDestroy
'})
{
$self
->
db_handle
->
disconnect
();
}
}
=head2 _get_adaptor
Arg [1] : string $module
...
...
@@ -418,10 +544,6 @@ sub locator {
sub
_get_adaptor
{
my
(
$self
,
$module
,
@args
)
=
@_
;
if
(
$self
->
isa
('
Bio::EnsEMBL::Container
'))
{
$self
=
$self
->
_obj
;
}
my
(
$adaptor
,
$internal_name
);
#Create a private member variable name for the adaptor by replacing
...
...
@@ -431,7 +553,7 @@ sub _get_adaptor {
$internal_name
=~
s/::/_/g
;
unless
(
defined
$self
->
{'
_adaptors
'}{
$internal_name
})
{
unless
(
defined
$self
->
{'
_adaptors
'}
->
{
$internal_name
})
{
eval
"
require
$module
";
if
(
$@
)
{
...
...
@@ -441,65 +563,13 @@ sub _get_adaptor {
$adaptor
=
"
$module
"
->
new
(
$self
,
@args
);
$self
->
{'
_adaptors
'}{
$internal_name
}
=
$adaptor
;
$self
->
{'
_adaptors
'}
->
{
$internal_name
}
=
$adaptor
;
}
return
$self
->
{'
_adaptors
'}{
$internal_name
};
}
=head2 db_handle
Arg [1] : DBI Database Handle $value
Example : $dbh = $db_connection->db_handle()
Description: Getter / Setter for the Database handle used by this
database connection.
Returntype : DBI Database Handle
Exceptions : none
Caller : new, DESTROY
=cut
sub
db_handle
{
my
(
$self
,
$value
)
=
@_
;
if
(
defined
$value
)
{
$self
->
{'
_db_handle
'}
=
$value
;
}
else
{
$self
->
connect
();
}
return
$self
->
{'
_db_handle
'};
return
$self
->
{'
_adaptors
'}
->
{
$internal_name
};
}
=head2 prepare
Arg [1] : string $string
the SQL statement to prepare
Example : $sth = $db_connection->prepare("SELECT column FROM table");
Description: Prepares a SQL statement using the internal DBI database handle
and returns the DBI statement handle.
Returntype : DBI statement handle
Exceptions : thrown if the SQL statement is empty, or if the internal
database handle is not present
Caller : Adaptor modules
=cut
sub
prepare
{
my
(
$self
,
$string
)
=
@_
;
if
(
!
$string
)
{
throw
("
Attempting to prepare an empty SQL query.
");
}
if
(
!
defined
$self
->
db_handle
)
{
throw
("
Database object has lost its database handle.
");
}
#info("SQL(".$self->dbname."):$string");
return
$self
->
db_handle
->
prepare
(
$string
);
}
=head2 add_db_adaptor
...
...
@@ -521,8 +591,8 @@ sub add_db_adaptor {
unless
(
$name
&&
$adaptor
&&
ref
$adaptor
)
{
throw
('
adaptor and name arguments are required
');
}
}
#avoid circular references and memory leaks
if
(
$adaptor
->
isa
('
Bio::EnsEMBL::Container
'))
{
$adaptor
=
$adaptor
->
_obj
;
...
...
@@ -573,7 +643,7 @@ sub remove_db_adaptor {
=cut
sub
get_all_db_adaptors
{
my
(
$self
)
=
@_
;
my
(
$self
)
=
@_
;
unless
(
defined
$self
->
{'
_db_adaptors
'})
{
return
{};
...
...
@@ -608,12 +678,15 @@ sub get_db_adaptor {
}
#
# This method is automatically called by the Container DESTROY method to
# break circular references. Do not call it directly.
#
sub
deleteObj
{
my
$self
=
shift
;
#print STDERR "DBConnection::deleteObj : Breaking circular references:\n";
if
(
exists
(
$self
->
{'
_adaptors
'}))
{
foreach
my
$adaptor_name
(
keys
%
{
$self
->
{'
_adaptors
'}})
{
my
$adaptor
=
$self
->
{'
_adaptors
'}
->
{
$adaptor_name
};
...
...
@@ -657,46 +730,19 @@ sub DESTROY {
#print STDERR "DESTROYING DBConnection\n";
$obj
->
disconnect
();
}
=head2 disconnect
Example : $db_connection->disconnect()
Description: Explicitely disconnect from database if connected
Returntype : none
Exceptions : none
Caller : ?, DESTROY
=cut
sub
disconnect
{
my
$self
=
shift
;
my
$dbh
=
$obj
->
db_handle
();
my
$dbh
=
$self
->
{'
_db_handle
'};
if
(
$dbh
)
{
my
$refcount
=
$self
->
ref_count
();
$$refcount
--
;
#print STDERR "DBConnection : ref_count-- ";
if
(
$dbh
)
{
# don't disconnect if InactiveDestroy flag is set, this can really
# screw up forked processes
if
(
!
$dbh
->
{'
InactiveDestroy
'})
{
$dbh
->
disconnect
();
}
}
# Do not disconnect if the InactiveDestroy flag has been set
# this can really screw up forked processes.
# Also: do not disconnect if this database handle is shared by
# other DBConnections (as indicated by the refcount)
if
(
!
$dbh
->
{'
InactiveDestroy
'}
&&
$$refcount
==
0
)
{
$dbh
->
disconnect
;
#print STDERR ": DISCONNECT ";
}
#print STDERR "\n";
$obj
->
db_handle
(
undef
);
}
$self
->
{'
_db_handle
'}
=
undef
;
#unlink shared ref_count variable since no longer sharing db_handle
my
$unlinked_refcount
=
0
;
$self
->
ref_count
(
\
$unlinked_refcount
);
}
}
1
;
This diff is collapsed.
Click to expand it.
modules/Bio/EnsEMBL/DBSQL/StatementHandle.pm
0 → 100644
+
62
−
0
View file @
6f907f92
=head1 NAME - Bio::EnsEMBL::DBSQL::StatementHandle
=head1 SYNOPSIS
Do not use this class directly. It will automatically be used by
the Bio::EnsEMBL::DBSQL::DBConnection class.
=head1 DESCRIPTION
This class extends DBD::mysql::st so that the DESTROY method may be
overridden. If the DBConnection::disconnect_when_inactive flag is set
this statement handle will cause the database connection to be closed
when it goes out of scope and there are no other open statement handles.
=head1 CONTACT
This module is part of the Ensembl project: www.ensembl.org
Ensembl development mailing list: <ensembl-dev@ebi.ac.uk>
=head1 METHODS
=cut
package
Bio::EnsEMBL::DBSQL::
StatementHandle
;
use
vars
qw(@ISA)
;
use
strict
;
use
DBD::
mysql
;
@ISA
=
qw(DBD::mysql::st)
;
# As DBD::mysql::st is a tied hash can't store things in it,
# so have to have parallel hash
my
%dbchash
;
sub
dbc
{
my
$self
=
shift
;
if
(
@
_
)
{
$dbchash
{
$self
}
=
shift
;
}
return
$dbchash
{
$self
};
}
sub
DESTROY
{
my
(
$obj
)
=
@_
;
DBD::mysql::st::
DESTROY
(
$obj
);
my
$dbc
=
$obj
->
dbc
;
# print STDERR "StatementHandle destroy $obj $dbc\n";
if
(
$dbc
&&
$dbc
->
disconnect_when_inactive
)
{
$dbc
->
disconnect_if_idle
;
}
$obj
->
dbc
(
undef
);
}
1
;
This diff is collapsed.
Click to expand it.
Preview
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment