package User;

use strict;
use warnings;
use Carp;
use SqlEngine2;
use Class::Throwable;

use User::Team;

our ($VERSION) = 0.5; 

=head1 NAME

User - a class that represents a sanger user that exists in one of the
Oracle databases.

=head1 SYNOPSIS

  use User;

  my $userObj = new User(personId=>137,roleType=>'ExoSeq');

  print "Full Name: ", $userObj->getFullName(), "\n";
  print "Forename : ", $userObj->getForename(), "\n";
  print "Surname  : ", $userObj->getSurname(),  "\n";
  print "Room     : ", $userObj->getRoom(),     "\n";
  print "Phone    : ", $userObj->getPhone(),    "\n";

  if($userObj->hasMultipleTeams())
  {
      print "User is registerd to work for multiple teams\n";

      ## Get a list of the teams this user is associated with.
      my $teamList = $userObj->getTeamList;

      my $count = 1;
      ## Show the user a menu of teams to pick from.
      foreach my $team (@$teamList) {
  	print $count++ . ": Team" . $team->getTeamName() . "\n";
      }

      my $teamChosen;

      until($teamChosen) {
  	print "Select a team: ";
  	chomp($teamChosen=<STDIN>);

  	if($teamChosen < 1 || $teamChosen > scalar(@{$userObj->getTeamList()})) {
  	    $teamChosen = undef;
  	}
      }

      ## Set the working team and role.
      $userObj->setWorkingTeam($teamList->[($teamChosen-1)],'ExoSeq');
  }

  print STDERR "\nWorking team has been set to:\n";
  print STDERR "Team Name: ", $userObj->getWorkingTeam()->getTeamName(),                   "\n";
  print STDERR "Role Id  : ", $userObj->getWorkingTeam()->getWorkingRole()->getRoleId(),   "\n";
  print STDERR "Role Type: ", $userObj->getWorkingTeam()->getWorkingRole()->getRoleType(), "\n\n";

=head1 DESCRIPTION

This class is the gateway to the User library. This library represents
a database application user. The User library is populated by creating
a User using any of the valid constructors shown below.

This class will attempt to populate the User library using the
following class tree structure.


 User 1 ----->  1..* User::Team 1 -----> 0..* User::Team::Role
                       1
                       |
                       +----> 1 User::Printer::BarcodePrinter
                                    |
                                    |
                      inherits from |
                                    |
                                    |
                User::Printer <-----+


The User object may contain one or more User::Team objects. The
User::Team objects represent the details of a Sanger Institute team
(see User::Team) and may contain a User::Printer::BarcodePrinter
object that represents the User::Team objects default barcode printer
(the name of the default barcode printer is found in the
DEFAULT_PRINTERS@CAS database table).

The programmer should use the
$user->setWorkingTeam(User::Team,roleType) method to set the working
team and role for the session. This is useful when multiple User::Team
and User::Team::Role objects are found.

If a User object finds a single User::Team and User::Team::Role object
then they are auto set to be the working team and role for the
session.

=head1 CONSTANTS

=over 10

=item User::LOGIN

This is the login string that is used to connect to the database.

=cut

use constant LOGIN => 'reports';


=item TRUE

Boolean true.

=cut

use constant TRUE  => 1;


=item FALSE

Boolean false.

=back

=cut

use constant FALSE => 0;


=item PERSONID

The index into each inner array returned by the getUserData() method
of the id_person (PERSON.id_person) value (see getUserData() method).

=cut

use constant PERSONID => 0;


=item EMAIL

The index into each inner array returned by the getUserData() method
of the email (PERSON.email) value (see getUserData() method).

=cut

use constant EMAIL => 1;


=item FORENAME

The index into each inner array returned by the getUserData() method
of the forename (PERSON.forename) value (see getUserData() method).

=cut

use constant FORENAME => 2;


=item SURNAME

The index into each inner array returned by the getUserData() method
of the surname (PERSON.surname) value (see getUserData() method).

=cut

use constant SURNAME  => 3;


=item ROOM

The index into each inner array returned by the getUserData() method
of the room (PERSON.room) value (see getUserData() method).

=cut

use constant ROOM => 4;


=item PHONE

The index into each inner array returned by the getUserData() method
of the phone (PERSON.phone) value (see getUserData() method).

=cut

use constant PHONE => 5;


=item TPTEAMNAME

The index into each inner array returned by the getUserData() method
of the team person team name (TEAM_PERSON.teamname) value (see
getUserData() method).

=cut

use constant TPTEAMNAME => 6;


=item TPRTEAMNAME

The index into each inner array returned by the getUserData() method
of the team person role team name (TEAM_PERSON_ROLE.teamname) value
(see getUserData() method).

=cut

use constant TPRTEAMNAME => 7;


=item ROLEID

The index into each inner array returned by the getUserData() method
of the role id (TEAM_PERSON_ROLE.id_role) value (see getUserData()
method).

=cut

use constant ROLEID => 8;


=item ROLETYPE

The index into each inner array returned by the getUserData() method
of the role type (TEAM_PERSON_ROLE.roletype) value (see getUserData()
method).

=cut

use constant ROLETYPE => 9;


=head1 VERBOSITY

Set the following variables to a true value to get verbose information
on database connection.

=over 10

=item $Class::Throwable::Debug

Default: FALSE
See Class::Throwable class.

=cut

$Class::Throwable::Debug = FALSE;


=item $SqlEngine2::verbosity

Default: FALSE
See SqlEngine2 class.

=back

=cut

$SqlEngine2::verbosity   = FALSE;


=head1 CONSTRUCTOR

The constructor arguments are divided into two sections, required and
optional. Combining a required argument with an optional argument will
narrow the search for a users details.

=head2 REQUIRED ARGUMENTS

Only one of the following arguments should be passed to the
constructor. If more than one of these arguments is passed then the
first one (in the order documented here) will be used.

=over 10

=item User->new(noPopulate => [TRUE || FALSE])

Required. This argument will prevent this class from performing any
lookups to find a users details. The result is an empty, unpopulated,
User object. If arguments personId and/or email are supplied with this
argument then they are simply set as object attributes. Other
arguments are disgarded.

=item User->new(personId => ?)

Required. This argument is a user id number as found in the
PERSON.id_person column. This is a users unique identifier. This
should be a number.

=item User->new(email => ?)

Required. This argument is a users email name as found in the
PERSON.email column. This value should be unique, but there have been
instances where this has not been the case.

=item User->new(roleId => ?)

Required. This argument is a users role identifier that is directly
associated to them. The value can be found in the
TEAM_PERSON_ROLE.id_role column and should be a number.

=item User->new(dbh => ?)

Optional. A database handle that will be used to perform lookups. If
this argument is not passed then LOGIN is used by this objects
SqlEngine2 object to generate a dbh.

=back

=head2 OPTIONAL ARGUMENTS

These arguments may be combined with the selected required argument to
narrow the search for user details.

=over 10

=item User->new(roleType => ["role"||qr//])

Optional. This argument should be combined with a required
argument. This argument is a string role type as found in the
TEAM_PERSON_ROLE.roletype column. A role type is a permission that is
given to an id_role which in turn is associated to a user in the
TEAM_PERSON_ROLE table and denotes a permission.

This argument may also be a regular expression. If this is the case
then the users details are looked up using the required argument
value. Each of the roles associated with the user are then passed over
the regular expression. Roles that do not match the regular expression
are disgarded.

=back

=head1 METHODS

=over 10

=cut

{
    my %_attributes = (
		       personId    => undef,
		       email       => undef,
		       forename    => undef,
		       surname     => undef,
		       phone       => undef,
		       room        => undef,

		       workingTeam => undef,
		       teamList    => []
		       );

    my %object;

    ## Autoloader wannabe. Inserts the sub calls that I want to be
    ## accessible into the symbol table.
    foreach my $attName (keys(%_attributes)) {
	no strict 'refs';
	*{__PACKAGE__ . "::get\u$attName"} = sub { $object{ $_[0] }->{$attName} }
    }


=item User->new(arg => value,... => ?);

This is the constructor for the User class (see CONSTRUCTOR section
for more information on arguments). This method calls the _init(%args)
method which allows deriving classes access to the argument list
passed to the constructor (see User::_init(%args) method). An error is
thrown if there are still arguments left in the %args hash after the
_init() method has been called.

If the isPopulated() method returns boolean true (see isPopulated()
method) then the populate() method is called (see populate() method)
to start the population process. Otherwise the object is considered a
placeholder.

=cut

    sub new {
	my ($proto,%args) = @_;
	my $class = ref($proto) || $proto;
	my $self  = bless {}, $class;
	%args = $self->_init(%args);
	my @leftover = keys %args;
	confess "Error: the following arguments are unknown - @leftover\n" if @leftover;
	## Attempt to populate the library.
	if ($self->isPopulated) {
	    eval  { $self->populate(); };
	    if($@){ croak $@;           }
	}
	return $self;
    }


=item $user->_init(%args);

Private method. Documented to aid sub classing. Derived classes should
implement their own _init() methods that removes the arguments they
are interested in from the passed %args hash. The %args hash should be
returned from _init() to populate the base class:

Derived class:

sub _init {
    my ($self,%args) = @_;
    my $classAttribute = delete $args{classAttribute};
    return $self->SUPER::_init(%args);
}

This method checks that at least one of the required arguments has
been passed. It also checks that the personId and roleId values, if
passed, are numbers.

This method creates a new SqlEngine2 object and sets it using the
setSqlEngine() method (see User::setSqlEngine(SqlEngine->new())
method).

=cut

    sub _init {
	my ($self,%args) = @_;
	foreach my $attName (keys(%args)) {
	    if(exists($_attributes{$attName})) {
		$object{ $self }->{$attName} = delete $args{$attName};
	    }
	}

	## Parsed var that is not an attribute of this object, so will
	## not get set by the above.
	$object{ $self }->{noPopulate} = delete $args{noPopulate} if $args{noPopulate};
	$object{ $self }->{roleType}   = delete $args{roleType}   if $args{roleType};
	$object{ $self }->{roleId}     = delete $args{roleId}     if $args{roleId};

	## Attribute personId is required if the email attribute is
	## missing.
	die "Error: argument 'personId', 'email' or 'roleId' is required."
	    unless $object{ $self }->{email} || $object{ $self }->{personId} ||
	    $object{ $self }->{roleId} || $object{ $self }->{noPopulate};

	## Check that the person id is a number.
	die "Error: argument 'personId' is not a number."
	  if $object{ $self }->{personId} && $object{ $self }->{personId} !~ /^\d+$/;

	## Check that the role id is a number.
	die "Error: argument 'roleId' is not a number."
	  if $object{ $self }->{roleId} && $object{ $self }->{roleId} !~ /^\d+$/;

	$self->setSqlEngine(new SqlEngine2);
	#$object{ $self }->{sqlObj} = new SqlEngine2();

	## Set the database handle for lookups.
#	$object{ $self }->{dbh} = $args{dbh}
#	  ? delete $args{dbh}
#	    : SqlEngine2::getDbh(LOGIN);

	return %args;
    }

=item $user->getPersonId()

Method to return the users unique id number.

=item $user->getEmail()

Method to return the users email/login.

=item $user->getForename()

Method to return the users forename.

=item $user->getSurname()

Method to return the users surname.

=item $user->getFullName()

Method to return the users concatenated forename and surname (with a space).

=item $user->getPhone()

Method to return the users phone number.

=item $user->getRoom()

Method to return the users room.

=item $user->getTeamList()

Method to return the list of User::Team objects found to have an
association with this User.


=item $user->setSqlEngine(SqlEngine2->new());

This method overrides the classes SqlEngine2 object used for database
lookups. This method is called by the _init method during class
initialisation (see _init() method).

=cut

    sub setSqlEngine {
	my ($self,$sqlEngine) = @_;
	die "Error: argument must be an SqlEngine2 type object"
	  unless UNIVERSAL::isa($sqlEngine,"SqlEngine2");
	$object{ $self }->{sqlObj} = $sqlEngine;
    }


=item $user->isPopulated();

This method returns boolean false if the noPopulate attribute is set
to true, otherwise boolean true.

=cut

    sub isPopulated {
	my $self = shift;
	return $object{ $self }->{noPopulate} ? FALSE : TRUE;
    }


=item $user->populate();

This method populates the User object with the objects representing
the data found using the object attributes. User data is found by
calling the getUserData() method (see getUserData() method).

If the roleType argument has been passed and it's a regular expression
then each role found in the user data returned by the getUserData()
method is matched against it. If the match is successful then the role
is retained, otherwise it's discarded.

This method finishes by calling the private _autoSetWorking() method
to attempt to set the working team and role (see the _autoSetWorking()
method).

There's no apparent reason why this method should be called outside of
the object. The data used to populate the object are set at during
instantiation and can not be changed during the life of the
object. So, this method is documented in case you wish to override the
way in which this class populates itself.

Returns nothing.

=cut

    sub populate {
	my $self         = shift;
	my $index        = 0;
	my %team         = ();
	my $userData     = $self->getUserData();
	my $roleTypeName = $object{ $self }->{roleType};
#	my $personId     = 0;
#	my $email        = 1;
#	my $forename     = 2;
#	my $surname      = 3;
#	my $room         = 4;
#	my $phone        = 5;
#	my $tpTeamName   = 6;
#	my $tprTeamName  = 7;
#	my $roleId       = 8;
#	my $roleType     = 9;
	$userData        = matchPattern
	    ($userData,$roleTypeName,User::ROLETYPE)
	    if ref $roleTypeName eq 'Regexp';
	foreach my $data (@{$userData}) {
	    $object{ $self }->{personId} = $data->[User::PERSONID] || undef unless $object{ $self }->{personId};
	    $object{ $self }->{email}    = $data->[User::EMAIL]    || undef unless $object{ $self }->{email};
	    $object{ $self }->{forename} = $data->[User::FORENAME] || undef unless $object{ $self }->{forename};
	    $object{ $self }->{surname}  = $data->[User::SURNAME]  || undef unless $object{ $self }->{surname};
	    $object{ $self }->{room}     = $data->[User::ROOM]     || undef unless $object{ $self }->{room};
	    $object{ $self }->{phone}    = $data->[User::PHONE]    || undef unless $object{ $self }->{phone};
	    if(!exists($team{$data->[User::TPRTEAMNAME]})) {
		$index++ if defined $object{ $self }->{teamList}->[$index];
		$object{ $self }->{teamList}->[$index] =
		  $self->getNewTeam
		    ( $data->[User::TPRTEAMNAME],
		     ($data->[User::TPRTEAMNAME] == $data->[User::TPTEAMNAME]));
		die "Error: team object is not of type User::Team"
		  unless UNIVERSAL::isa($object{ $self }->{teamList}->[$index],"User::Team");
		$team{$data->[User::TPRTEAMNAME]} = 1;
	    }

	    $object{ $self }->{teamList}->[$index]
		->addNewRole
		  ($data->[User::ROLEID],
		   $data->[User::ROLETYPE]);

	    #User::Team::Role->new
	    #(roleId   =>$data->[User::ROLEID],
	    # roleType =>$data->[User::ROLETYPE]));
	}
	$self->_autoSetWorking();
    }


=item $user->getNewTeam($teamName,$isDefault);

This method is called by the object to instantiate and return a new
User::Team object. Override this method to return a derived User::Team
class. The object always checks the type of object returned by this
method, so it should always return an object that derives from the
User::Team class.

=cut

    sub getNewTeam {
	my ($self,$teamName,$isDefault) = @_;
	return User::Team->new
	  (teamName => $teamName,
	   default  => $isDefault);
	   #dbh      => $object{ $self }->{dbh});
    }


=item $user->matchPattern(\@userData,qr/pattern/,$index);

This method removes inner arrays from the userData array reference
thats value at element $index DO NOT match the pattern. The userData
array reference should be as returned by the getUserData() method (see
getUserData() method). This method is by default called by the
populate method to remove data associated with roles that do not match
the passed roleType regular expression - if one was passed.

The new array ref of user data minus any matching data is passed to
the private _verifyUserData() method for validation before being
returned (see _verifyUserData() method).

=cut

    sub matchPattern {
	my ($userData,$pattern,$index) = @_;
	my @newUserData = ();
	foreach(@$userData) {
	    my $data = $_->[$index];
	    if($data =~ $pattern) {
		push(@newUserData,$_);
	    }
	}
	return _verifyUserData(\@newUserData,["pattern"]);
    }


=item $user->getUserData();

This   method   calls  either   the   _executeSingleLookup()  or   the
_executeRoleTypeLookup() methods depending on the object state. If the
object  was  instantiated  with  only  a required  argument  then  the
_executeSingleLookup()    method     is    called,    otherwise    the
_executeRoleTypeLookup() method is called. Both methods are passed the
type of required argument being used i.e. personId, email or roleId.

Returns an array or arrays containing the found user data (see
CONSTANTS for indexes and thus orders of the inner array elements).

=cut

    sub getUserData {
	my $self = shift;
	my $userData;
	## Execute the correct SQL. If we have the personId then that
	## takes precedence. Include the role type if parsed.
	foreach my $attName ('personId','email','roleId') {
	    if($object{ $self }->{$attName}) {
		my $roleType = $object{ $self }->{roleType};
		my $rdType   = ref $roleType;
		if($roleType && !$rdType) {
		    $userData = $self->_executeRoleTypeLookup($attName);
		}else{
		    $userData = $self->_executeSingleLookup($attName);
		}
		last;
	    }
	}
	return $userData;
    }


=item $user->setWorkingTeam(User::Team,[User::Team::Role || "roleType"]);

This method sets the passed User::Team object as the working team for
the session or until this method is called with a different User::Team
and role.

The role is an optional parameter. If passed, the role may be either a
User::Team::Role object or role type string name. This method calls
the User::Team::setWorkingRole() method to set the teams working role
(see User::Team::setWorkingRole() method).

Subsequent calls to $user->getWorkingTeam() will return the User::Team
object passed into this method.

=cut

    sub setWorkingTeam {
	my ($self,$team,$role) = @_;
	die "ERROR: requires a 'User::Team' object."
	    unless $team->isa('User::Team');
	$team->setWorkingRole($role) if $role;
	$object{ $self }->{workingTeam} = $team;
    }


=item $user->getDefaultTeam();

During population (see the population() method) User::Team objects are
created to represent each of the teams that the user belongs
to. "Belongs to" is defined by a single entry in the TEAM_PERSON table
(TEAM_PERSON.teamname) and associated teams via the TEAM_PERSON_ROLE
table (TEAM_PERSON_ROLE.teamname). The TEAM_PERSON table holds the
users default team details.

This method returns the User::Team object that represents the users
default team as described above. All users should have an entry in the
TEAM_PERSON table so all populated User objects should have a default
User::Team object. Exceptions to this rule will occurr with the use of
the roleType argument that may exclude the default team due to it not
having any roles of the specific type.

=cut

    sub getDefaultTeam {
	my $self = shift;
	foreach my $team (@{$object{ $self }->{teamList}}) {
	    if($team->isDefault()) {
		return $team;
	    }
	}
    }


=item $user->hasMultipleTeams(roleType);

This method returns boolean true if there are more than one User::Team
object in the team list (see getTeamList() method) and they each have
one or more User::Team::Role objects (using the roleType argument with
a regular expression may result in a User::Team object that has no
User::Team::Role objects).

=cut

    sub hasMultipleTeams {
	my ($self,$roleType) = @_;
	my $roleCount = 0;
	if($roleType) {
	    foreach my $team (@{$object{ $self }->{teamList}}) {
		if($team->hasRoleType($roleType)) {
		    $roleCount++;
		}
	    }
	    return $roleCount > 1 ? TRUE : FALSE;
	}
	return scalar(@{$object{ $self }->{teamList}}) > 1 ? TRUE : FALSE;
    }


=item $user->getTeamWithRole(roleType);

This method returns a list of User::Team objects that have a
User::Team::Role object that's User::Team::Role::getRoleType() method
matches the passed roleType string (see
User::Team::Role::getRoleType() method).

=cut

    sub getTeamWithRole {
	my ($self,$roleType) = @_;
	my @teamList=();
	foreach my $team (@{$object{ $self }->{teamList}}) {
	    if($team->hasRoleType($roleType)) {
		push(@teamList, $team);
	    }
	}
	return \@teamList;
    }


=item $user->getFullName();

This method returns the forename and surname of the user concatenated
with a space between them.

=cut

    sub getFullName {
	my $self = shift;
	return $object{ $self }->{forename} . " " . $object{ $self }->{surname};
    }


=item $user->validatePassword(passwordAttempt);

This method is passed a string password to be checked against the
encrypted system password for the user represented by this object.

The encrypted system password is retrieved using getpwnam(). The two
character salt is extracted and the passed password is encrypted. If
the two encrypted passwords are the same, then boolean true is
returned, otherwise false is returned.

The passed password is not retained by the object after leaving this
method.

=cut

    sub validatePassword {
	my ($self,$password) = @_;
	my $email = $self->getEmail;
	my $encryptedPassword = (getpwnam($email))[1];
	my $salt = substr($encryptedPassword,0,2);
	if (crypt($password,$salt) ne $encryptedPassword) {
	    return FALSE;
	}
	return TRUE;
    }


=item $user->_autoSetWorking();

Private method. This method attempts to automatically set the working
User::Team and User::Team::Role objects. This can only be done if
there was one User::Team object containing only one User::Team::Role
object found. This method is guaranteed to set the working team and
role if the roleId argument is specified as the sole required argument
during object instantiation. This is because a role id number id
associated with only one role that can only be associated with one
person working for one team.

If successful, this method will call the setWorkingTeam(User::Team)
method (see setWorkingTeam(User::Team) method. This method will set
the working role.

Returns nothing.

=cut

    sub _autoSetWorking {
	my $self = shift;
	## If there is only one team and one role then set them as
	## working.
	if(scalar(@{$object{ $self }->{teamList}})==1) {
	    my $team = $object{ $self }->{teamList}->[0];
	    if(scalar(@{$team->getRoleList()})==1) {
		my $role = $team->getRoleList()->[0];
		$self->setWorkingTeam($team,$role->getRoleType());
	    }
	}
    }


=item $user->_verifyUserData(\@userData,\@args);

Private method. This method verifies that the passed array ref of user
data (as returned by getUserData() method) contains some data. If it
does not then the object dies with the generic message "no user data
found using " with the contents of the args array ref joined to the
end.

Returns the user data if it contains some data otherwise dies.

=cut

    sub _verifyUserData {
	my ($userData,$args) = @_;
	if(scalar(@{$userData})==0) {
	    die "no user data found using @$args\n";
	}
	return $userData;
    }


=item $user->_executeSingleLookup(type);

Private method. This method executes an SQL statement that requires
only one of the required arguments (see REQUIRED ARGUMENTS) defined by
type. The SQL statement name is created by concatenating the
"UserData_" string with the passed type. New types need only be given
an SQL statement with a name following this convention (the SQL
statement should also return the same columns as other statements -
see CONSTANTS for a list of columns to return and their order).

The data found by executing the SQL statement is passed to the
_verifyUserData() method (see _verifyUserData() method).

Returns an array reference of arrays containing the user data.

=cut

    sub _executeSingleLookup {
	my ($self,$type) = @_;
	my $userData = eval { 
	    $object{ $self }->{sqlObj}->getAll
		("User::UserData_" . $type,
		 [$object{ $self }->{$type}],
		 SqlEngine2::getDbh(LOGIN));
	};
	if($@) { croak "Error: can't execute single type lookup using '$type': $@\n"; }
	eval   { _verifyUserData($userData,[$type,$object{ $self }->{$type}]); };
	if($@) { croak $@;                           }
	return $userData;
    }


=item $user->_executeRoleTypeLookup(type);

Private method. This method executes an SQL statement that requires a
required argument (defined by type) as well as a role type (as set at
object instantiation) (see REQUIRED ARGUMENTS and OPTIONAL
ARGUMENTS). The SQL statement name is created by concatenating the
"RoleType_" string with the passed type. New types need only be given
an SQL statement with a name following this convention (the SQL
statement should also return the same columns as other statements -
see CONSTANTS for a list of columns to return and their order).

The data found by executing the SQL statement is passed to the
_verifyUserData() method (see _verifyUserData() method).

Returns an array reference of arrays containing the user data.

=cut

    sub _executeRoleTypeLookup {
	my ($self,$type) = @_;
	my $userData = eval { 
	    $object{ $self }->{sqlObj}->getAll
		("User::UserData_RoleType_" . $type,
		 [$object{ $self }->{$type},
		  $object{ $self }->{roleType}],
		 SqlEngine2::getDbh(LOGIN));
	};
	if($@) { croak "Error: can't execute role type lookup using '$type' and ",
		 $object{ $self }->{roleType}, ": $@\n"; }
	eval   { _verifyUserData($userData,[$type,"with roleType $object{ $self }->{roleType}"]); };
	if($@) { croak $@;                                                      }
	return $userData;
    }


=item DESTROY

Destructor method that deletes this objects internal structure before
the objects is deleted. If you derive from this class please call
$self->SUPER::DESTROY() to ensure this object is tidied up correctly.

=cut

    sub DESTROY {
	my $self = shift;
	delete $object{ $self };
    }
}

1;


=head1 SEE ALSO

User::Team, User::Team::Role, User::Printer, User::Printer::BarcodePrinter

=head1 AUTHOR

Simon Holroyd <holroyd@sanger.ac.uk>

=head1 HISTORY

 # Log: $

=cut
