package UR::Singleton; use strict; use warnings; require UR; our $VERSION = "0.47"; # UR $VERSION; UR::Object::Type->define( class_name => 'UR::Singleton', is => ['UR::Object'], is_abstract => 1, ); sub id { my $self = shift; return (ref $self ? $self->SUPER::id(@_) : $self); } sub _init_subclass { my $class_name = shift; my $class_meta_object = $class_name->__meta__; # Write into the class's namespace the correct singleton overrides # to standard UR::Object methods. my $src; if ($class_meta_object->is_abstract) { $src = qq|sub ${class_name}::_singleton_object { Carp::confess("${class_name} is an abstract singleton! Select a concrete sub-class.") }| . "\n" . qq|sub ${class_name}::_singleton_class_name { Carp::confess("${class_name} is an abstract singleton! Select a concrete sub-class.") }| . "\n" . qq|sub ${class_name}::_load { shift->_abstract_load(\@_) }| } else { $src = qq|sub ${class_name}::_singleton_object { \$${class_name}::singleton or shift->_concrete_load() }| . "\n" . qq|sub ${class_name}::_singleton_class_name { '${class_name}' }| . "\n" . qq|sub ${class_name}::_load { shift->_concrete_load(\@_) }| . "\n" . qq|sub ${class_name}::get { shift->_concrete_get(\@_) }| . "\n" . qq|sub ${class_name}::is_loaded { shift->_concrete_is_loaded(\@_) }| ; } eval $src; Carp::confess($@) if $@; return 1; } # Abstract singletons havd a different load() method than concrete ones. # We could do this with forking logic, but since many of the concrete methods # get non-default handling, it's more efficient to do it this way. sub _abstract_load { my $class = shift; my $bx = $class->define_boolexpr(@_); my $id = $bx->value_for_id; unless (defined $id) { use Data::Dumper; my $params = { $bx->params_list }; Carp::confess("Cannot load a singleton ($class) except by specific identity. " . Dumper($params)); } my $subclass_name = $class->_resolve_subclass_name_for_id($id); eval "use $subclass_name"; if ($@) { undef $@; return; } return $subclass_name->get(); } # Concrete singletons have overrides to the most basic acccessors to # accomplish class/object duality smoothly. sub _concrete_get { if (@_ == 1 or (@_ == 2 and $_[0] eq $_[1])) { my $self = $_[0]->_singleton_object; return $self if $self; } return shift->_concrete_load(@_); } sub _concrete_is_loaded { if (@_ == 1 or (@_ == 2 and $_[0] eq $_[1])) { my $self = $_[0]->_singleton_object; return $self if $self; } return shift->SUPER::is_loaded(@_); } sub _concrete_load { my $class = shift; $class = ref($class) || $class; no strict 'refs'; my $varref = \${ $class . "::singleton" }; unless ($$varref) { my $id = $class->_resolve_id_for_subclass_name($class); my $class_object = $class->__meta__; my @prop_names = $class_object->all_property_names; my %default_values; foreach my $prop_name ( @prop_names ) { my $prop = $class_object->property_meta_for_name($prop_name); next unless $prop; my $val = $prop->{'default_value'}; next unless defined $val; $default_values{$prop_name} = $val; } $$varref = $UR::Context::current->_construct_object($class,%default_values, id => $id); $$varref->{db_committed} = { %$$varref }; $$varref->__signal_change__("load"); Scalar::Util::weaken($$varref); } my $self = $class->_concrete_is_loaded(@_); return unless $self; unless ($self->init) { Carp::confess("Failed to initialize singleton $class!"); } return $self; } # This is implemented in the singleton to do any post-load processing. sub init { return 1; } # All singletons require special deletion logic since they keep a #weakened reference to the singleton. sub delete { my $self = shift; my $class = $self->class; $self->SUPER::delete(); no strict 'refs'; ${ $class . "::singleton" } = undef if ${ $class . "::singleton" } eq $self; return $self; } # In most cases, the id is the class name itself, but this is not necessary. sub _resolve_subclass_name_for_id { my $class = shift; my $id = shift; return $id; } sub _resolve_id_for_subclass_name { my $class = shift; my $subclass_name = shift; return $subclass_name; } sub create { my $class = shift; my $bx = $class->define_boolexpr(@_); my $id = $bx->value_for_id; unless (defined $id) { Carp::confess("No singleton ID class specified for constructor?"); } my $subclass = $class->_resolve_subclass_name_for_id($id); eval "use $subclass"; unless ($subclass->isa(__PACKAGE__)) { eval '@' . $subclass . "::ISA = ('" . __PACKAGE__ . "')"; } return $subclass->_concrete_get(); } 1; =pod =head1 NAME UR::Singleton - Abstract class for implementing singleton objects =head1 SYNOPSIS package MyApp::SomeClass; use UR; class MyApp::SomeClass { is => 'UR::Singleton', has => [ foo => { is => 'Number' }, ] }; $obj = MyApp::SomeClass->get(); $obj->foo(1); =head1 DESCRIPTION This class provides the infrastructure for singleton classes. Singletons are classes of which there can only be one instance, and that instance's ID is the class name. If a class inherits from UR::Singleton, it overrides the default implementation of C and C in UR::Object with code that fabricates an appropriate object the first time it's needed. Singletons are most often used as one of the parent classes for data sources within a Namespace. This makes it convienent to refer to them using only their name, as in a class definition. =head1 METHODS =over 4 =item _singleton_object $obj = Class::Name->_singleton_object; $obj = $obj->_singleton_object; Returns the object instance whether it is called as a class or object method. =item _singleton_class_name $class_name = Class::Name->_singleton_class_name; $class_name = $obj->_singleton_class_name; Returns the class name whether it is called as a class or object method. =back =head1 SEE ALSO UR::Object =cut