Activator

 view release on metacpan or  search on metacpan

lib/Activator/Registry.pm  view on Meta::CPAN


  #### get a deep value for $key from default realm
  #### this form throws exception for invalid keys
  $key = 'top->deep->deeper';
  try eval {
     Activator::Registry->get( $key );
  }

  #### register YAML file into realm
  Activator::Registry->register_file( $file, $realm );

  #### register hash into realm
  Activator::Registry->register_hash( $mode, $hashref, $realm );

  #### use ${} syntax in your registry for variables
  Activator::Registry->replace_in_realm( 'default', $replacements_hashref );

=head1 DESCRIPTION

This module provides global access to a registry of key-value pairs.
It is implemented as a singleton, so you can use this Object Oriented
or staticly with arrow notation. It supports getting and setting of
deeply nested objects. Setting can be done via YAML configuration
files.

=head1 CONFIGURATION FILES

Configuration files are YAML files.

=head2 Registry Within Another Configuration File

You can have a registry be a stand alone file, or live within a
configuration file used for other purposes. If you wish your registry
to be only a subset of a larger YAML file, put the desired hierarchy
in a top level key C<Activator::Registry>. If that key exists, only
that part of the YAML file will be registered.

=head2 Default Configuration File

Often, your project will have a central configuration file that you
always want to use. In these cases set the environment variable
C<ACT_REG_YAML_FILE>. All calls to L</new()>, L</load()> and
L</reload()> will register this file first, then any files passed as
arguments to those subroutines.

If you are utilizing this module from apache, this directive must be
in your httpd configuration:

  SetEnv ACT_REG_YAML_FILE '/path/to/config.yml'

If you are using this module from a script, you need to ensure that
the environment is properly set. This my require that you utilize a
BEGIN block BEFORE the C<use> statement of any module that C<use>s
C<Activator::Registry> itself:

  BEGIN{
      $ENV{ACT_REG_YAML_FILE} ||= '/path/to/reg.yml'
  }

Otherwise, you will get weirdness when all of your expected registry
keys are undef...

=head1 METHODS

=head2 new()

Returns a reference to a registry object. This is a singleton, so
repeated calls always return the same ref. This will load the file
specified by C<$ENV{ACT_REG_YAML_FILE}>, then C<$yaml_file>. If
neither are valid YAML files, you will have an object with an empty
registry. If the registry has already been loaded, DOES NOT RELOAD it.
use L</reload()> for that.

=cut

sub new {
    my ( $pkg, $yaml_file ) = @_;

    my $self = bless( {
          DEFAULT_REALM => 'default',
	  REGISTRY => { },

# TODO: consider using this custom precedence:
#          SAFE_LEFT_PRECEDENCE =>
#           {
#            'SCALAR' => {
#               'SCALAR' => sub { $_[0] },
#               'ARRAY'  => &die_array_scalar,
#               'HASH'   => &die_hash_scalar,
#              },
#            'ARRAY' => {
#               'SCALAR' => sub { [ @{ $_[0] }, $_[1] ] },
#               'ARRAY'  => sub { [ @{ $_[0] }, @{ $_[1] } ] },
		       #               'HASH'   => &die_hash_array,
#              },
#            'HASH' => {
#               'SCALAR' => &die_scalar_hash,
#               'ARRAY'  => &die_array_hash,
#               'HASH'   => sub { _merge_hashes( $_[0], $_[1] ) },
#              },
#	  },

		      }, $pkg);

    $self->_init_StrongSingleton();
    if ( $yaml_file ) {
	$self->load( $yaml_file )
    }
    else {
	$self->load();
    }
    return $self;
}

=head2 load()

Load a YAML file into the registry. Throws exception if the file has
already been successfully loaded.

=cut

sub load {
    my ( $pkg, $yaml_file, $reload ) = @_;
    my $self = $pkg->new();
    my $registered_something;

    if( $reload ) {
	$self->{REGISTRY_BACKUP} = $self->{REGISTRY};
	$self->{REGISTRY} = { };
    }

    if ( !keys( %{ $self->{REGISTRY} } ) ) {

	if( defined ( $ENV{ACT_REG_YAML_FILE} ) && -f $ENV{ACT_REG_YAML_FILE} ) {
	    $self->register_file( $ENV{ACT_REG_YAML_FILE} );
	    $registered_something = 1;
	}

	if ( defined( $yaml_file ) && -f $yaml_file ) {
	    $self->register_file( $yaml_file );
	    $registered_something = 1;
	}
    }

    else {
	# refuse to reload without flag
	WARN("Cowardly refusing to stomp registry without 'reload' flag");
	return;
    }

    if ( !$registered_something ) {

	my $action = 'load';
	if ( keys %{ $self->{REGISTRY_BACKUP} } ) {
	    $self->{REGISTRY} = $self->{REGISTRY_BACKUP};
	    $action = 'reload';
	}

	if ( $ENV{ACT_REG_YAML_FILE} || $yaml_file ) {
	    my $msg = "Registry $action failed." .
	    'Neither $ENV{ACT_REG_YAML_FILE} ('. ( $ENV{ACT_REG_YAML_FILE} || 'undef' ) .
	    ') nor $yaml_file ('. ( $yaml_file || 'undef' ) .
	    ') are a valid configuration file';

	    # TODO: figure out how to solve the cyclic dependancy problem.
	    # That is, Log depends on this file to find it's config, so
	    # when calling new, we can't be guranteed that log is loaded.
	    # We need to figure out if Log is loaded, then we can just
	    # warn for the outlier case where Log is configured to a bad
	    # filename.
	    warn( "[WARN] $msg" );
	}
	$registered_something = 0;
    }
    else {
	$registered_something = 1;
    }

    return $registered_something;
}

=head2 reload()

Reloads a specific configuration file. This nukes the existing registry.

=cut

sub reload {
    my ( $pkg, $yaml_file ) = @_;
    $pkg->load( $yaml_file, 1 );
}

=head2 register( $key, $value, $realm )

Register a key-value pair to C<$realm>. Registers to the default realm
if C<$realm> not defined. Returns true on success, false otherwise
(more specifically, the return value of the C<eq> operator when
testing the set value to the value passed in).

=cut

sub register {
  my ($pkg, $key, $value, $realm) = @_;
  my $self = $pkg->new();
  $realm ||= $self->{DEFAULT_REALM};

   my @keys = split( '->', $key );
   if ( @keys > 1 ) {
       my $setref = $self->{REGISTRY}->{ $realm };
       $self->_deep_register( \@keys, $value, $setref );
       return $self->get( $key ) eq $value;
   }
  else {
      $self->{REGISTRY}->{ $realm }->{ $key } = $value;
      return $self->{REGISTRY}->{ $realm }->{ $key } eq $value;
  }
}

sub _deep_register {
  my ($self, $keys, $value, $setref) = @_;
  my $curkey = shift @$keys;
  if ( @$keys == 0 ) {

lib/Activator/Registry.pm  view on Meta::CPAN


    # In pre 1.0 versions of this module, it was a top level key of
    # 'Activator::Registry' was required to allow registries to live
    # within other yml files. In common usage, this is not the normal
    # case. Here we support both.
    if ( $config->{'Activator::Registry'} ) {
	$self->register_hash( 'left', $config->{'Activator::Registry'}, $realm );
    }
    else {
	$self->register_hash( 'left', $config, $realm );
    }
}


=head2 register_hash( $mode, $right, $realm)

Set registry keys in C<$realm> from C<$right> hash using C<$mode>,
which can either be C<left> or C<right>. C<left> will only set keys
that do not exist, and C<right> will set or override all C<$right>
values into C<$realm>'s registry.

=cut

sub register_hash {
    my ( $pkg, $mode, $right, $realm ) = @_;
    if ( $mode eq 'left' ) {
	Hash::Merge::set_behavior( 'LEFT_PRECEDENT' );
    }
    elsif ( $mode eq 'right' ) {
	Hash::Merge::set_behavior( 'RIGHT_PRECEDENT' );
    }
    else {
	# TODO: consider using custom precedence
	#Hash::Merge::specify_behavior( $pkg->{SAFE_LEFT_PRECEDENCE} );

	Activator::Exception::Registry->throw( 'mode', 'invalid' );
    }
    my $reg = $pkg->new();
    $realm ||= $reg->{DEFAULT_REALM};
    if ( !exists( $reg->{REGISTRY}->{ $realm } ) ) {
	$reg->{REGISTRY}->{ $realm } = {};
    }
    my $merged = {};
    try eval {
	$merged = Hash::Merge::merge( $reg->{REGISTRY}->{ $realm }, $right );
    };
    # catch
    if ( catch my $e ) {
	Activator::Exception::Registry->throw( 'merge', 'failure', $e );
    }

    elsif( keys %$merged ) {
	$reg->{REGISTRY}->{ $realm } = $merged;
    }
}

=head2 get( $key, $realm )

Get the value for C<$key> within C<$realm>. If C<$realm> not defined
returns the value from the default realm. C<$key> can refer to a
deeply nested element. Returns undef if the key does not exist, or you
try to seek into an array. Some examples:

With a YAML config that produces:

  deep_list:
    level_1:
      - level_2_a
      - level_2_b
  key: value

You will get this behavior:

  Activator::Registry->get( 'key' );                           # returns 'value'
  Activator::Registry->get( 'deep_list' );                     # returns hashref
  Activator::Registry->get( 'deep_lost' );                     # returns undef
  Activator::Registry->get( 'deep_list->level_1' );            # returns arrayref
  Activator::Registry->get( 'deep_list->level_1->level_2_a' ); # returns undef
  Activator::Registry->get( 'deep_list->level_one' );          # returns undef

=cut

sub get {
   my ($pkg, $key, $realm) = @_;

   my $self = $pkg->new();
   $realm ||= $self->{DEFAULT_REALM};

   my @keys = split( '->', $key );
   if ( @keys > 1 ) {
       my $retval;
       try eval {
	   $retval = $self->_deep_get( \@keys, $realm, $self->{REGISTRY}->{ $realm } );
       };
       if ( catch my $e ) {
	   return;
       }
       return $retval;
   }
   return $self->{REGISTRY}->{ $realm }->{ $key };
}

sub _deep_get {
   my ($pkg, $keys, $realm, $reg_ref) = @_;
   my $key = shift @$keys;

   if ( @$keys == 0 ) {
       if ( exists( $reg_ref->{ $key } ) ) {
	   return $reg_ref->{ $key };
       }
       else {
	   Activator::Exception::Registry->throw( 'key', 'invalid', $key );
       }
   }

   if ( exists( $reg_ref->{ $key } ) ) {
       return $pkg->_deep_get( $keys, $realm, $reg_ref->{ $key } );
   }
   else {
       Activator::Exception::Registry->throw( 'key', 'invalid', $key );
   }
}

=head2 get_realm( $realm )

Return a reference to hashref for an entire C<$realm>.

=cut

sub get_realm {
   my ($pkg, $realm) = @_;

   my $self = $pkg->new();
   $realm ||= $self->{DEFAULT_REALM};
   return $self->{REGISTRY}->{ $realm };
}


=head2 set_default_realm( $realm )



( run in 1.116 second using v1.01-cache-2.11-cpan-d80b1682f3f )